diff --git a/.gitmodules b/.gitmodules
index be6b9310a7874377e3871a65d28db01d1063973b..b64f1a6afca22fe370aedc912d735119c1b5bef1 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -22,14 +22,6 @@
 	path = modules/EntityFrameworkCore
 	url = https://github.com/aspnet/EntityFrameworkCore.git
 	branch = release/2.1
-[submodule "modules/Hosting"]
-	path = modules/Hosting
-	url = https://github.com/aspnet/Hosting.git
-	branch = release/2.1
-[submodule "modules/HttpAbstractions"]
-	path = modules/HttpAbstractions
-	url = https://github.com/aspnet/HttpAbstractions.git
-	branch = release/2.1
 [submodule "modules/HttpSysServer"]
 	path = modules/HttpSysServer
 	url = https://github.com/aspnet/HttpSysServer.git
diff --git a/Directory.Build.props b/Directory.Build.props
index 99d625f1ad5659390272422d7d35c3420d435737..1999dba76d08f30619e3a83f3faf5b2e5fe0c398 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -31,6 +31,8 @@
     <IncludeSource>false</IncludeSource>
     <IncludeSymbols>true</IncludeSymbols>
 
+    <SharedSourceRoot>$(MSBuildThisFileDirectory)src\Shared\</SharedSourceRoot>
+
     <SuppressNETCoreSdkPreviewMessage>true</SuppressNETCoreSdkPreviewMessage>
   </PropertyGroup>
 
diff --git a/build/artifacts.props b/build/artifacts.props
index 717198331b425392276e55cc4d2e74b25768227f..587242f9e54db22e63c2744f66872aed1b4fe04e 100644
--- a/build/artifacts.props
+++ b/build/artifacts.props
@@ -74,7 +74,6 @@
     <PackageArtifact Include="Microsoft.AspNetCore.HostFiltering" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Hosting.Abstractions" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" AllMetapackage="true" AppMetapackage="true" Category="ship" />
-    <PackageArtifact Include="Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources" Category="noship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Hosting.WindowsServices" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Hosting" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Html.Abstractions" AllMetapackage="true" AppMetapackage="true" Category="ship" />
@@ -134,7 +133,6 @@
     <PackageArtifact Include="Microsoft.AspNetCore.Routing" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Server.HttpSys" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Server.IISIntegration" AllMetapackage="true" AppMetapackage="true" Category="ship" />
-    <PackageArtifact Include="Microsoft.AspNetCore.Server.IntegrationTesting" Category="noship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Server.Kestrel.Core" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Server.Kestrel.Https" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions" AllMetapackage="true" AppMetapackage="true" Category="ship" />
@@ -183,8 +181,6 @@
     <PackageArtifact Include="Microsoft.Extensions.ApplicationModelDetection" Category="noship" />
     <PackageArtifact Include="Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions" Category="noship" />
     <PackageArtifact Include="Microsoft.Extensions.Diagnostics.HealthChecks" Category="noship" />
-    <PackageArtifact Include="Microsoft.Extensions.Hosting.Abstractions" AllMetapackage="true" AppMetapackage="true" Category="ship" />
-    <PackageArtifact Include="Microsoft.Extensions.Hosting" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.Extensions.Identity.Core" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.Extensions.Identity.Stores" AllMetapackage="true" AppMetapackage="true" Category="ship" />
     <PackageArtifact Include="Microsoft.Extensions.Localization.Abstractions" AllMetapackage="true" AppMetapackage="true" Category="ship" />
diff --git a/build/buildorder.props b/build/buildorder.props
index d5b5dab276bad9ca26fb36598d9840dc73c4139b..58a3f1f7664e754931f6ba2dd379c12df91ad0a2 100644
--- a/build/buildorder.props
+++ b/build/buildorder.props
@@ -8,8 +8,6 @@
 
   <ItemGroup>
     <RepositoryBuildOrder Include="Razor" Order="6" />
-    <RepositoryBuildOrder Include="HttpAbstractions" Order="6" />
-    <RepositoryBuildOrder Include="Hosting" Order="7" />
     <RepositoryBuildOrder Include="EntityFrameworkCore" Order="8" />
     <RepositoryBuildOrder Include="HttpSysServer" Order="8" />
     <RepositoryBuildOrder Include="BrowserLink" Order="8" />
diff --git a/build/dependencies.props b/build/dependencies.props
index b46e6e7a6c63454ec7727c51bbdb49ee17cc7d14..8b1e4119f1373527ce4021c048e2250719655a9c 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -44,13 +44,13 @@
     <MicrosoftExtensionsConfigurationIniPackageVersion>2.1.1</MicrosoftExtensionsConfigurationIniPackageVersion>
     <MicrosoftExtensionsConfigurationJsonPackageVersion>2.1.1</MicrosoftExtensionsConfigurationJsonPackageVersion>
     <MicrosoftExtensionsConfigurationKeyPerFilePackageVersion>2.1.1</MicrosoftExtensionsConfigurationKeyPerFilePackageVersion>
+    <MicrosoftExtensionsConfigurationPackageVersion>2.1.1</MicrosoftExtensionsConfigurationPackageVersion>
     <MicrosoftExtensionsConfigurationUserSecretsPackageVersion>2.1.1</MicrosoftExtensionsConfigurationUserSecretsPackageVersion>
     <MicrosoftExtensionsConfigurationXmlPackageVersion>2.1.1</MicrosoftExtensionsConfigurationXmlPackageVersion>
-    <MicrosoftExtensionsConfigurationPackageVersion>2.1.1</MicrosoftExtensionsConfigurationPackageVersion>
     <MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>2.1.1</MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion>
     <MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>2.1.1</MicrosoftExtensionsDependencyInjectionAbstractionsPackageVersion>
-    <MicrosoftExtensionsDependencyInjectionSpecificationTestsPackageVersion>2.1.1</MicrosoftExtensionsDependencyInjectionSpecificationTestsPackageVersion>
     <MicrosoftExtensionsDependencyInjectionPackageVersion>2.1.1</MicrosoftExtensionsDependencyInjectionPackageVersion>
+    <MicrosoftExtensionsDependencyInjectionSpecificationTestsPackageVersion>2.1.1</MicrosoftExtensionsDependencyInjectionSpecificationTestsPackageVersion>
     <MicrosoftExtensionsDiagnosticAdapterPackageVersion>2.1.0</MicrosoftExtensionsDiagnosticAdapterPackageVersion>
     <MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>2.1.1</MicrosoftExtensionsFileProvidersAbstractionsPackageVersion>
     <MicrosoftExtensionsFileProvidersCompositePackageVersion>2.1.1</MicrosoftExtensionsFileProvidersCompositePackageVersion>
@@ -58,6 +58,8 @@
     <MicrosoftExtensionsFileProvidersPhysicalPackageVersion>2.1.1</MicrosoftExtensionsFileProvidersPhysicalPackageVersion>
     <MicrosoftExtensionsFileSystemGlobbingPackageVersion>2.1.1</MicrosoftExtensionsFileSystemGlobbingPackageVersion>
     <MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>2.1.1</MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion>
+    <MicrosoftExtensionsHostingAbstractionsPackageVersion>2.1.1</MicrosoftExtensionsHostingAbstractionsPackageVersion>
+    <MicrosoftExtensionsHostingPackageVersion>2.1.1</MicrosoftExtensionsHostingPackageVersion>
     <MicrosoftExtensionsHttpPackageVersion>2.1.1</MicrosoftExtensionsHttpPackageVersion>
     <MicrosoftExtensionsLoggingAbstractionsPackageVersion>2.1.1</MicrosoftExtensionsLoggingAbstractionsPackageVersion>
     <MicrosoftExtensionsLoggingAzureAppServicesPackageVersion>2.1.1</MicrosoftExtensionsLoggingAzureAppServicesPackageVersion>
@@ -66,9 +68,9 @@
     <MicrosoftExtensionsLoggingDebugPackageVersion>2.1.1</MicrosoftExtensionsLoggingDebugPackageVersion>
     <MicrosoftExtensionsLoggingEventLogPackageVersion>2.1.1</MicrosoftExtensionsLoggingEventLogPackageVersion>
     <MicrosoftExtensionsLoggingEventSourcePackageVersion>2.1.1</MicrosoftExtensionsLoggingEventSourcePackageVersion>
+    <MicrosoftExtensionsLoggingPackageVersion>2.1.1</MicrosoftExtensionsLoggingPackageVersion>
     <MicrosoftExtensionsLoggingTestingPackageVersion>2.1.1</MicrosoftExtensionsLoggingTestingPackageVersion>
     <MicrosoftExtensionsLoggingTraceSourcePackageVersion>2.1.1</MicrosoftExtensionsLoggingTraceSourcePackageVersion>
-    <MicrosoftExtensionsLoggingPackageVersion>2.1.1</MicrosoftExtensionsLoggingPackageVersion>
     <MicrosoftExtensionsObjectMethodExecutorSourcesPackageVersion>2.1.1</MicrosoftExtensionsObjectMethodExecutorSourcesPackageVersion>
     <MicrosoftExtensionsObjectPoolPackageVersion>2.1.6</MicrosoftExtensionsObjectPoolPackageVersion>
     <MicrosoftExtensionsOptionsConfigurationExtensionsPackageVersion>2.1.1</MicrosoftExtensionsOptionsConfigurationExtensionsPackageVersion>
@@ -83,10 +85,13 @@
     <MicrosoftExtensionsStackTraceSourcesPackageVersion>2.1.1</MicrosoftExtensionsStackTraceSourcesPackageVersion>
     <MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>2.1.1</MicrosoftExtensionsTypeNameHelperSourcesPackageVersion>
     <MicrosoftExtensionsValueStopwatchSourcesPackageVersion>2.1.1</MicrosoftExtensionsValueStopwatchSourcesPackageVersion>
-    <MicrosoftExtensionsWebEncodersSourcesPackageVersion>2.1.1</MicrosoftExtensionsWebEncodersSourcesPackageVersion>
     <MicrosoftExtensionsWebEncodersPackageVersion>2.1.1</MicrosoftExtensionsWebEncodersPackageVersion>
+    <MicrosoftExtensionsWebEncodersSourcesPackageVersion>2.1.1</MicrosoftExtensionsWebEncodersSourcesPackageVersion>
 
+    <!-- These dependencies are temporary while we refactor package refs into project refs. -->
     <MicrosoftExtensionsBuffersTestingSourcesPackageVersion>2.1.1</MicrosoftExtensionsBuffersTestingSourcesPackageVersion>
+    <MicrosoftAspNetCoreHostingWebHostBuilderFactorySourcesPackageVersion>2.1.1</MicrosoftAspNetCoreHostingWebHostBuilderFactorySourcesPackageVersion>
+    <MicrosoftAspNetCoreServerIntegrationTestingPackageVersion>0.5.1</MicrosoftAspNetCoreServerIntegrationTestingPackageVersion>
 
     <!-- External and partner dependencies -->
     <AngleSharpPackageVersion>0.9.9</AngleSharpPackageVersion>
diff --git a/build/external-dependencies.props b/build/external-dependencies.props
index 32846302dd1ce27bf19f5e2db63117f572d9ccd8..684b4db40b4eaf425a242b3fcb32104f0d4bcb30 100644
--- a/build/external-dependencies.props
+++ b/build/external-dependencies.props
@@ -58,6 +58,8 @@
     <ExtensionsDependency Include="Microsoft.Extensions.FileProviders.Physical" Version="$(MicrosoftExtensionsFileProvidersPhysicalPackageVersion)"  AllMetapackage="true" AppMetapackage="true" />
     <ExtensionsDependency Include="Microsoft.Extensions.FileSystemGlobbing" Version="$(MicrosoftExtensionsFileSystemGlobbingPackageVersion)"  AllMetapackage="true" AppMetapackage="true" />
     <ExtensionsDependency Include="Microsoft.Extensions.HashCodeCombiner.Sources" Version="$(MicrosoftExtensionsHashCodeCombinerSourcesPackageVersion)" />
+    <ExtensionsDependency Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(MicrosoftExtensionsHostingAbstractionsPackageVersion)" AllMetapackage="true" AppMetapackage="true" />
+    <ExtensionsDependency Include="Microsoft.Extensions.Hosting" Version="$(MicrosoftExtensionsHostingPackageVersion)" AllMetapackage="true" AppMetapackage="true" />
     <ExtensionsDependency Include="Microsoft.Extensions.Http" Version="$(MicrosoftExtensionsHttpPackageVersion)" AllMetapackage="true" AppMetapackage="true" />
     <ExtensionsDependency Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsLoggingAbstractionsPackageVersion)"  AllMetapackage="true" AppMetapackage="true" />
     <ExtensionsDependency Include="Microsoft.Extensions.Logging.AzureAppServices" Version="$(MicrosoftExtensionsLoggingAzureAppServicesPackageVersion)"  AllMetapackage="true" />
@@ -86,7 +88,10 @@
     <ExtensionsDependency Include="Microsoft.Extensions.WebEncoders.Sources" Version="$(MicrosoftExtensionsWebEncodersSourcesPackageVersion)" />
     <ExtensionsDependency Include="Microsoft.Extensions.WebEncoders" Version="$(MicrosoftExtensionsWebEncodersPackageVersion)" AllMetapackage="true" AppMetapackage="true" />
 
+    <!-- These dependencies are temporary while we refactor package refs into project refs. -->
     <ExtensionsDependency Include="Microsoft.Extensions.Buffers.Testing.Sources" Version="$(MicrosoftExtensionsBuffersTestingSourcesPackageVersion)" />
+    <ExtensionsDependency Include="Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources" Version="$(MicrosoftAspNetCoreHostingWebHostBuilderFactorySourcesPackageVersion)" />
+    <ExtensionsDependency Include="Microsoft.AspNetCore.Server.IntegrationTesting" Version="$(MicrosoftAspNetCoreServerIntegrationTestingPackageVersion)" />
   </ItemGroup>
 
    <ItemGroup>
diff --git a/build/repo.props b/build/repo.props
index 1d5261431b8bcef99c1db924d5881cc42baafa89..a1b4241fbba4498fc2e0fbd0a1a6bab4f67a52b0 100644
--- a/build/repo.props
+++ b/build/repo.props
@@ -42,7 +42,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <SamplesProject Include="$(RepositoryRoot)src\samples\**\*.csproj;"/>
+    <SamplesProject Include="$(RepositoryRoot)src\**\samples\**\*.csproj;"/>
 
     <ProjectToExclude Include="@(SamplesProject)" Condition="'$(BuildSamples)' == 'false' "/>
 
@@ -55,6 +55,8 @@
     <ProjectToBuild Include="
                       $(RepositoryRoot)src\Features\JsonPatch\**\*.*proj;
                       $(RepositoryRoot)src\DataProtection\**\*.*proj;
+                      $(RepositoryRoot)src\Hosting\**\*.*proj;
+                      $(RepositoryRoot)src\Http\**\*.*proj;
                       $(RepositoryRoot)src\Html\**\*.*proj;
                       $(RepositoryRoot)src\Servers\**\*.*proj;
                       $(RepositoryRoot)src\Tools\**\*.*proj;
diff --git a/build/submodules.props b/build/submodules.props
index ccc78ab2e8f8758020b7437b6fe9d4facabb8e29..638753659f5c9d426fcb8307de9ade51733f2916 100644
--- a/build/submodules.props
+++ b/build/submodules.props
@@ -56,8 +56,6 @@
     <ShippedRepository Include="CORS" />
     <ShippedRepository Include="Diagnostics" />
     <ShippedRepository Include="EntityFrameworkCore" />
-    <ShippedRepository Include="Hosting" />
-    <ShippedRepository Include="HttpAbstractions" />
     <ShippedRepository Include="HttpSysServer" />
     <ShippedRepository Include="Identity" />
     <ShippedRepository Include="JavaScriptServices" RootPath="$(RepositoryRoot)src\JavaScriptServices\" />
diff --git a/eng/Baseline.props b/eng/Baseline.props
index a8f9316a92b70a2cfb3e07c2e23d91092db79f51..9d9a2241b278c019ef217f493f01d91dc6d053a5 100644
--- a/eng/Baseline.props
+++ b/eng/Baseline.props
@@ -24,6 +24,24 @@
     <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
   </PropertyGroup>
   <ItemGroup Condition=" '$(PackageId)' == 'dotnet-watch' AND '$(TargetFramework)' == 'netcoreapp2.1' " />
+  <!-- Package: Microsoft.AspNetCore.Authentication.Abstractions-->
+  <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Authentication.Abstractions' ">
+    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+  </PropertyGroup>
+  <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Authentication.Abstractions' AND '$(TargetFramework)' == 'netstandard2.0' ">
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Options" Version="[2.1.1, )" />
+  </ItemGroup>
+  <!-- Package: Microsoft.AspNetCore.Authentication.Core-->
+  <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Authentication.Core' ">
+    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+  </PropertyGroup>
+  <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Authentication.Core' AND '$(TargetFramework)' == 'netstandard2.0' ">
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Authentication.Abstractions" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http" Version="[2.1.1, )" />
+  </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.Connections.Abstractions-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Connections.Abstractions' ">
     <BaselinePackageVersion>2.1.3</BaselinePackageVersion>
@@ -108,6 +126,53 @@
     <BaselinePackageReference Include="System.Security.Cryptography.Xml" Version="[4.5.0, )" />
     <BaselinePackageReference Include="System.Security.Principal.Windows" Version="[4.5.0, )" />
   </ItemGroup>
+  <!-- Package: Microsoft.AspNetCore.Hosting.Abstractions-->
+  <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.Abstractions' ">
+    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+  </PropertyGroup>
+  <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.Abstractions' AND '$(TargetFramework)' == 'netstandard2.0' ">
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="[2.1.1, )" />
+  </ItemGroup>
+  <!-- Package: Microsoft.AspNetCore.Hosting.Server.Abstractions-->
+  <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.Server.Abstractions' ">
+    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+  </PropertyGroup>
+  <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.Server.Abstractions' AND '$(TargetFramework)' == 'netstandard2.0' ">
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Features" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="[2.1.1, )" />
+  </ItemGroup>
+  <!-- Package: Microsoft.AspNetCore.Hosting.WindowsServices-->
+  <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.WindowsServices' ">
+    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+  </PropertyGroup>
+  <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.WindowsServices' AND '$(TargetFramework)' == 'net461' ">
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Hosting" Version="[2.1.1, )" />
+  </ItemGroup>
+  <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting.WindowsServices' AND '$(TargetFramework)' == 'netstandard2.0' ">
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Hosting" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="System.ServiceProcess.ServiceController" Version="[4.5.0, )" />
+  </ItemGroup>
+  <!-- Package: Microsoft.AspNetCore.Hosting-->
+  <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting' ">
+    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+  </PropertyGroup>
+  <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Hosting' AND '$(TargetFramework)' == 'netstandard2.0' ">
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Configuration" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.DependencyInjection" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.FileProviders.Physical" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Logging" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Options" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="System.Diagnostics.DiagnosticSource" Version="[4.5.0, )" />
+    <BaselinePackageReference Include="System.Reflection.Metadata" Version="[1.6.0, )" />
+  </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.Html.Abstractions-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Html.Abstractions' ">
     <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
@@ -115,6 +180,42 @@
   <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Html.Abstractions' AND '$(TargetFramework)' == 'netstandard2.0' ">
     <BaselinePackageReference Include="System.Text.Encodings.Web" Version="[4.5.0, )" />
   </ItemGroup>
+  <!-- Package: Microsoft.AspNetCore.Http.Abstractions-->
+  <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Abstractions' ">
+    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+  </PropertyGroup>
+  <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Abstractions' AND '$(TargetFramework)' == 'netstandard2.0' ">
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Features" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="System.Text.Encodings.Web" Version="[4.5.0, )" />
+  </ItemGroup>
+  <!-- Package: Microsoft.AspNetCore.Http.Extensions-->
+  <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Extensions' ">
+    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+  </PropertyGroup>
+  <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Extensions' AND '$(TargetFramework)' == 'netstandard2.0' ">
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Net.Http.Headers" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.FileProviders.Abstractions" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="System.Buffers" Version="[4.5.0, )" />
+  </ItemGroup>
+  <!-- Package: Microsoft.AspNetCore.Http.Features-->
+  <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Features' ">
+    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+  </PropertyGroup>
+  <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http.Features' AND '$(TargetFramework)' == 'netstandard2.0' ">
+    <BaselinePackageReference Include="Microsoft.Extensions.Primitives" Version="[2.1.1, )" />
+  </ItemGroup>
+  <!-- Package: Microsoft.AspNetCore.Http-->
+  <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http' ">
+    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+  </PropertyGroup>
+  <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Http' AND '$(TargetFramework)' == 'netstandard2.0' ">
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Net.Http.Headers" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.ObjectPool" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="Microsoft.Extensions.Options" Version="[2.1.1, )" />
+  </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.JsonPatch-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.JsonPatch' ">
     <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
@@ -123,6 +224,13 @@
     <BaselinePackageReference Include="Microsoft.CSharp" Version="[4.5.0, )" />
     <BaselinePackageReference Include="Newtonsoft.Json" Version="[11.0.2, )" />
   </ItemGroup>
+  <!-- Package: Microsoft.AspNetCore.Owin-->
+  <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Owin' ">
+    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+  </PropertyGroup>
+  <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Owin' AND '$(TargetFramework)' == 'netstandard2.0' ">
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Http" Version="[2.1.1, )" />
+  </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.Server.Kestrel.Core-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.Server.Kestrel.Core' ">
     <BaselinePackageVersion>2.1.3</BaselinePackageVersion>
@@ -209,6 +317,14 @@
     <BaselinePackageReference Include="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" Version="[2.1.3, )" />
     <BaselinePackageReference Include="Microsoft.AspNetCore.Hosting" Version="[2.1.1, )" />
   </ItemGroup>
+  <!-- Package: Microsoft.AspNetCore.TestHost-->
+  <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.TestHost' ">
+    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+  </PropertyGroup>
+  <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.TestHost' AND '$(TargetFramework)' == 'netstandard2.0' ">
+    <BaselinePackageReference Include="Microsoft.AspNetCore.Hosting" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="System.IO.Pipelines" Version="[4.5.0, )" />
+  </ItemGroup>
   <!-- Package: Microsoft.AspNetCore.WebSockets-->
   <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.WebSockets' ">
     <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
@@ -218,4 +334,20 @@
     <BaselinePackageReference Include="Microsoft.Extensions.Options" Version="[2.1.1, )" />
     <BaselinePackageReference Include="System.Net.WebSockets.WebSocketProtocol" Version="[4.5.1, )" />
   </ItemGroup>
+  <!-- Package: Microsoft.AspNetCore.WebUtilities-->
+  <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.WebUtilities' ">
+    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+  </PropertyGroup>
+  <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.AspNetCore.WebUtilities' AND '$(TargetFramework)' == 'netstandard2.0' ">
+    <BaselinePackageReference Include="Microsoft.Net.Http.Headers" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="System.Text.Encodings.Web" Version="[4.5.0, )" />
+  </ItemGroup>
+  <!-- Package: Microsoft.Net.Http.Headers-->
+  <PropertyGroup Condition=" '$(PackageId)' == 'Microsoft.Net.Http.Headers' ">
+    <BaselinePackageVersion>2.1.1</BaselinePackageVersion>
+  </PropertyGroup>
+  <ItemGroup Condition=" '$(PackageId)' == 'Microsoft.Net.Http.Headers' AND '$(TargetFramework)' == 'netstandard2.0' ">
+    <BaselinePackageReference Include="Microsoft.Extensions.Primitives" Version="[2.1.1, )" />
+    <BaselinePackageReference Include="System.Buffers" Version="[4.5.0, )" />
+  </ItemGroup>
 </Project>
\ No newline at end of file
diff --git a/eng/Dependencies.props b/eng/Dependencies.props
index c2792fb38420bd9dc94e6e2b945178ec39d02dcd..0ad0e061110d0f3bd130c0acc27c996dfccfed4e 100644
--- a/eng/Dependencies.props
+++ b/eng/Dependencies.props
@@ -15,19 +15,28 @@
     <LatestPackageReference Include="Microsoft.CSharp" Version="$(MicrosoftCSharpPackageVersion)" />
     <LatestPackageReference Include="Microsoft.Extensions.ActivatorUtilities.Sources" Version="$(MicrosoftExtensionsActivatorUtilitiesSourcesPackageVersion)" />
     <LatestPackageReference Include="Microsoft.Extensions.ClosedGenericMatcher.Sources" Version="$(MicrosoftExtensionsClosedGenericMatcherSourcesPackageVersion)" />
+    <LatestPackageReference Include="Microsoft.Extensions.CopyOnWriteDictionary.Sources" Version="$(MicrosoftExtensionsCopyOnWriteDictionarySourcesPackageVersion)" />
     <LatestPackageReference Include="Microsoft.Extensions.CommandLineUtils.Sources" Version="$(MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion)" />
     <LatestPackageReference Include="Microsoft.Extensions.Configuration.CommandLine" Version="$(MicrosoftExtensionsConfigurationCommandLinePackageVersion)" />
+    <LatestPackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="$(MicrosoftExtensionsConfigurationEnvironmentVariablesPackageVersion)" />
     <LatestPackageReference Include="Microsoft.Extensions.Configuration.Json" Version="$(MicrosoftExtensionsConfigurationJsonPackageVersion)" />
     <LatestPackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="$(MicrosoftExtensionsConfigurationUserSecretsPackageVersion)" />
     <LatestPackageReference Include="Microsoft.Extensions.Configuration" Version="$(MicrosoftExtensionsConfigurationPackageVersion)" />
     <LatestPackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(MicrosoftExtensionsDependencyInjectionPackageVersion)" />
+    <LatestPackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="$(MicrosoftExtensionsDiagnosticAdapterPackageVersion)" />
+    <LatestPackageReference Include="Microsoft.Extensions.Hosting" Version="$(MicrosoftExtensionsHostingPackageVersion)" />
     <LatestPackageReference Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsolePackageVersion)" />
     <LatestPackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(MicrosoftExtensionsLoggingTestingPackageVersion)" />
     <LatestPackageReference Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingPackageVersion)" />
+    <LatestPackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="$(MicrosoftExtensionsFileProvidersEmbeddedPackageVersion)" />
     <LatestPackageReference Include="Microsoft.Extensions.Options" Version="$(MicrosoftExtensionsOptionsPackageVersion)" />
     <LatestPackageReference Include="Microsoft.Extensions.Process.Sources" Version="$(MicrosoftExtensionsProcessSourcesPackageVersion)" />
+    <LatestPackageReference Include="Microsoft.Extensions.RazorViews.Sources" Version="$(MicrosoftExtensionsRazorViewsSourcesPackageVersion)" />
+    <LatestPackageReference Include="Microsoft.Extensions.StackTrace.Sources" Version="$(MicrosoftExtensionsStackTraceSourcesPackageVersion)" />
+    <LatestPackageReference Include="Microsoft.Extensions.TypeNameHelper.Sources" Version="$(MicrosoftExtensionsTypeNameHelperSourcesPackageVersion)" />
     <LatestPackageReference Include="Microsoft.Extensions.WebEncoders.Sources" Version="$(MicrosoftExtensionsWebEncodersSourcesPackageVersion)" />
     <LatestPackageReference Include="Microsoft.Extensions.WebEncoders" Version="$(MicrosoftExtensionsWebEncodersPackageVersion)" />
+    <LatestPackageReference Include="Microsoft.NETCore.Windows.ApiSets" Version="$(MicrosoftNETCoreWindowsApiSetsPackageVersion)" />
     <LatestPackageReference Include="System.Data.SqlClient" Version="$(SystemDataSqlClientPackageVersion)" />
     <LatestPackageReference Include="System.Memory" Version="$(SystemMemoryPackageVersion)" />
     <LatestPackageReference Include="System.Net.WebSockets.WebSocketProtocol" Version="$(SystemNetWebSocketsWebSocketProtocolPackageVersion)" />
@@ -44,6 +53,8 @@
     <LatestPackageReference Include="Newtonsoft.Json" Version="9.0.1" Condition="'$(UseMSBuildJsonNet)' == 'true'" />
     <!-- This version should be used by runtime packages -->
     <LatestPackageReference Include="Newtonsoft.Json" Version="11.0.2" Condition="'$(UseMSBuildJsonNet)' != 'true'" />
+    <LatestPackageReference Include="Serilog.Extensions.Logging" Version="$(SerilogExtensionsLoggingPackageVersion)" />
+    <LatestPackageReference Include="Serilog.Sinks.File" Version="$(SerilogSinksFilePackageVersion)" />
     <LatestPackageReference Include="Utf8Json" Version="1.3.7" />
     <LatestPackageReference Include="xunit.abstractions" Version="2.0.1" />
     <LatestPackageReference Include="xunit.analyzers" Version="0.10.0" />
diff --git a/eng/ProjectReferences.props b/eng/ProjectReferences.props
index 7d04ed99b8fbe940afc0a4bc4f2bc354b5ee970f..0e25409b66ce89682bc1b7ff3d7cae686860752a 100644
--- a/eng/ProjectReferences.props
+++ b/eng/ProjectReferences.props
@@ -11,6 +11,21 @@
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.DataProtection.Extensions" ProjectPath="$(RepositoryRoot)src\DataProtection\Extensions\src\Microsoft.AspNetCore.DataProtection.Extensions.csproj" />
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.DataProtection.Redis" ProjectPath="$(RepositoryRoot)src\DataProtection\Redis\src\Microsoft.AspNetCore.DataProtection.Redis.csproj" />
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.DataProtection.SystemWeb" ProjectPath="$(RepositoryRoot)src\DataProtection\SystemWeb\src\Microsoft.AspNetCore.DataProtection.SystemWeb.csproj" />
+    <ProjectReferenceProvider Include="Microsoft.AspNetCore.Hosting.Abstractions" ProjectPath="$(RepositoryRoot)src\Hosting\Abstractions\src\Microsoft.AspNetCore.Hosting.Abstractions.csproj" />
+    <ProjectReferenceProvider Include="Microsoft.AspNetCore.Hosting" ProjectPath="$(RepositoryRoot)src\Hosting\Hosting\src\Microsoft.AspNetCore.Hosting.csproj" />
+    <ProjectReferenceProvider Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" ProjectPath="$(RepositoryRoot)src\Hosting\Server.Abstractions\src\Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj" />
+    <ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.IntegrationTesting" ProjectPath="$(RepositoryRoot)src\Hosting\Server.IntegrationTesting\src\Microsoft.AspNetCore.Server.IntegrationTesting.csproj" />
+    <ProjectReferenceProvider Include="Microsoft.AspNetCore.TestHost" ProjectPath="$(RepositoryRoot)src\Hosting\TestHost\src\Microsoft.AspNetCore.TestHost.csproj" />
+    <ProjectReferenceProvider Include="Microsoft.AspNetCore.Hosting.WindowsServices" ProjectPath="$(RepositoryRoot)src\Hosting\WindowsServices\src\Microsoft.AspNetCore.Hosting.WindowsServices.csproj" />
+    <ProjectReferenceProvider Include="Microsoft.AspNetCore.Authentication.Abstractions" ProjectPath="$(RepositoryRoot)src\Http\Authentication.Abstractions\src\Microsoft.AspNetCore.Authentication.Abstractions.csproj" />
+    <ProjectReferenceProvider Include="Microsoft.AspNetCore.Authentication.Core" ProjectPath="$(RepositoryRoot)src\Http\Authentication.Core\src\Microsoft.AspNetCore.Authentication.Core.csproj" />
+    <ProjectReferenceProvider Include="Microsoft.Net.Http.Headers" ProjectPath="$(RepositoryRoot)src\Http\Headers\src\Microsoft.Net.Http.Headers.csproj" />
+    <ProjectReferenceProvider Include="Microsoft.AspNetCore.Http.Abstractions" ProjectPath="$(RepositoryRoot)src\Http\Http.Abstractions\src\Microsoft.AspNetCore.Http.Abstractions.csproj" />
+    <ProjectReferenceProvider Include="Microsoft.AspNetCore.Http.Extensions" ProjectPath="$(RepositoryRoot)src\Http\Http.Extensions\src\Microsoft.AspNetCore.Http.Extensions.csproj" />
+    <ProjectReferenceProvider Include="Microsoft.AspNetCore.Http.Features" ProjectPath="$(RepositoryRoot)src\Http\Http.Features\src\Microsoft.AspNetCore.Http.Features.csproj" />
+    <ProjectReferenceProvider Include="Microsoft.AspNetCore.Http" ProjectPath="$(RepositoryRoot)src\Http\Http\src\Microsoft.AspNetCore.Http.csproj" />
+    <ProjectReferenceProvider Include="Microsoft.AspNetCore.Owin" ProjectPath="$(RepositoryRoot)src\Http\Owin\src\Microsoft.AspNetCore.Owin.csproj" />
+    <ProjectReferenceProvider Include="Microsoft.AspNetCore.WebUtilities" ProjectPath="$(RepositoryRoot)src\Http\WebUtilities\src\Microsoft.AspNetCore.WebUtilities.csproj" />
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.Html.Abstractions" ProjectPath="$(RepositoryRoot)src\Html\Abstractions\src\Microsoft.AspNetCore.Html.Abstractions.csproj" />
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.Connections.Abstractions" ProjectPath="$(RepositoryRoot)src\Servers\Connections.Abstractions\src\Microsoft.AspNetCore.Connections.Abstractions.csproj" />
     <ProjectReferenceProvider Include="Microsoft.AspNetCore.Server.Kestrel.Core" ProjectPath="$(RepositoryRoot)src\Servers\Kestrel\Core\src\Microsoft.AspNetCore.Server.Kestrel.Core.csproj" />
diff --git a/eng/dependencies.temp.props b/eng/dependencies.temp.props
index a96067429f096ffde50b8b1cd31f14014ca0f4c0..f319c9348c3a1bb0107bac8266afa0702ad110db 100644
--- a/eng/dependencies.temp.props
+++ b/eng/dependencies.temp.props
@@ -1,5 +1,5 @@
 <!--
-This file is temporary until aspnet/Hosting, Diagnostics, StaticFiles, and HttpAbstractions are merged into this repo.
+This file is temporary until aspnet/Diagnostics and StaticFiles are merged into this repo.
 This is required to provide dependencies for samples and tests.
  -->
 <Project>
@@ -7,9 +7,5 @@ This is required to provide dependencies for samples and tests.
     <LatestPackageReference Include="Microsoft.AspNetCore.Diagnostics" Version="2.1.1" />
     <LatestPackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="2.1.1" />
     <LatestPackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.1.1" />
-    <LatestPackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.1.1" />
-    <LatestPackageReference Include="Microsoft.AspNetCore.Http.Features" Version="2.1.1" />
-    <LatestPackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.1.1" />
-    <LatestPackageReference Include="Microsoft.AspNetCore.Http" Version="2.1.1" />
   </ItemGroup>
 </Project>
diff --git a/eng/tools/BaselineGenerator/baseline.xml b/eng/tools/BaselineGenerator/baseline.xml
index 823c131a302693f2903a3dfb5a180734bdcbb5c2..5a3e9661b96d372ce379b6baf7967e0f9729bbf8 100644
--- a/eng/tools/BaselineGenerator/baseline.xml
+++ b/eng/tools/BaselineGenerator/baseline.xml
@@ -3,6 +3,8 @@
   <Package Id="dotnet-sql-cache" Version="2.1.1" />
   <Package Id="dotnet-user-secrets" Version="2.1.1" />
   <Package Id="dotnet-watch" Version="2.1.1" />
+  <Package Id="Microsoft.AspNetCore.Authentication.Abstractions" Version="2.1.1" />
+  <Package Id="Microsoft.AspNetCore.Authentication.Core" Version="2.1.1" />
   <Package Id="Microsoft.AspNetCore.Connections.Abstractions" Version="2.1.3" />
   <Package Id="Microsoft.AspNetCore.Cryptography.Internal" Version="2.1.1" />
   <Package Id="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="2.1.1" />
@@ -13,13 +15,25 @@
   <Package Id="Microsoft.AspNetCore.DataProtection.Redis" Version="0.4.1" />
   <Package Id="Microsoft.AspNetCore.DataProtection.SystemWeb" Version="2.1.1" />
   <Package Id="Microsoft.AspNetCore.DataProtection" Version="2.1.1" />
+  <Package Id="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.1.1" />
+  <Package Id="Microsoft.AspNetCore.Hosting.Server.Abstractions" Version="2.1.1" />
+  <Package Id="Microsoft.AspNetCore.Hosting.WindowsServices" Version="2.1.1" />
+  <Package Id="Microsoft.AspNetCore.Hosting" Version="2.1.1" />
   <Package Id="Microsoft.AspNetCore.Html.Abstractions" Version="2.1.1" />
+  <Package Id="Microsoft.AspNetCore.Http.Abstractions" Version="2.1.1" />
+  <Package Id="Microsoft.AspNetCore.Http.Extensions" Version="2.1.1" />
+  <Package Id="Microsoft.AspNetCore.Http.Features" Version="2.1.1" />
+  <Package Id="Microsoft.AspNetCore.Http" Version="2.1.1" />
   <Package Id="Microsoft.AspNetCore.JsonPatch" Version="2.1.1" />
+  <Package Id="Microsoft.AspNetCore.Owin" Version="2.1.1" />
   <Package Id="Microsoft.AspNetCore.Server.Kestrel.Core" Version="2.1.3" />
   <Package Id="Microsoft.AspNetCore.Server.Kestrel.Https" Version="2.1.3" />
   <Package Id="Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions" Version="2.1.3" />
   <Package Id="Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv" Version="2.1.3" />
   <Package Id="Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets" Version="2.1.3" />
   <Package Id="Microsoft.AspNetCore.Server.Kestrel" Version="2.1.3" />
+  <Package Id="Microsoft.AspNetCore.TestHost" Version="2.1.1" />
   <Package Id="Microsoft.AspNetCore.WebSockets" Version="2.1.1" />
+  <Package Id="Microsoft.AspNetCore.WebUtilities" Version="2.1.1" />
+  <Package Id="Microsoft.Net.Http.Headers" Version="2.1.1" />
 </Baseline>
diff --git a/modules/Hosting b/modules/Hosting
deleted file mode 160000
index 3f7ee338d4cdd1c49bb965338ad7b118fa070a83..0000000000000000000000000000000000000000
--- a/modules/Hosting
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 3f7ee338d4cdd1c49bb965338ad7b118fa070a83
diff --git a/modules/HttpAbstractions b/modules/HttpAbstractions
deleted file mode 160000
index d142d58eb43626961117136c51993d51dfb7371d..0000000000000000000000000000000000000000
--- a/modules/HttpAbstractions
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit d142d58eb43626961117136c51993d51dfb7371d
diff --git a/src/Hosting/Abstractions/src/EnvironmentName.cs b/src/Hosting/Abstractions/src/EnvironmentName.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d5522d11240ed7a9088b1573b0feb07ba8c36d7e
--- /dev/null
+++ b/src/Hosting/Abstractions/src/EnvironmentName.cs
@@ -0,0 +1,15 @@
+// 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.Hosting
+{
+    /// <summary>
+    /// Commonly used environment names.
+    /// </summary>
+    public static class EnvironmentName
+    {
+        public static readonly string Development = "Development";
+        public static readonly string Staging = "Staging";
+        public static readonly string Production = "Production";
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Abstractions/src/HostingAbstractionsWebHostBuilderExtensions.cs b/src/Hosting/Abstractions/src/HostingAbstractionsWebHostBuilderExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f61b86eb862648cf702c0d3086853ef62a42e1bc
--- /dev/null
+++ b/src/Hosting/Abstractions/src/HostingAbstractionsWebHostBuilderExtensions.cs
@@ -0,0 +1,197 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Threading;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    public static class HostingAbstractionsWebHostBuilderExtensions
+    {
+        private static readonly string ServerUrlsSeparator = ";";
+
+        /// <summary>
+        /// Use the given configuration settings on the web host.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
+        /// <param name="configuration">The <see cref="IConfiguration"/> containing settings to be used.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder UseConfiguration(this IWebHostBuilder hostBuilder, IConfiguration configuration)
+        {
+            foreach (var setting in configuration.AsEnumerable())
+            {
+                hostBuilder.UseSetting(setting.Key, setting.Value);
+            }
+
+            return hostBuilder;
+        }
+
+        /// <summary>
+        /// Set whether startup errors should be captured in the configuration settings of the web host.
+        /// When enabled, startup exceptions will be caught and an error page will be returned. If disabled, startup exceptions will be propagated.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
+        /// <param name="captureStartupErrors"><c>true</c> to use startup error page; otherwise <c>false</c>.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder CaptureStartupErrors(this IWebHostBuilder hostBuilder, bool captureStartupErrors)
+        {
+            return hostBuilder.UseSetting(WebHostDefaults.CaptureStartupErrorsKey, captureStartupErrors ? "true" : "false");
+        }
+
+        /// <summary>
+        /// Specify the assembly containing the startup type to be used by the web host.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
+        /// <param name="startupAssemblyName">The name of the assembly containing the startup type.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, string startupAssemblyName)
+        {
+            if (startupAssemblyName == null)
+            {
+                throw new ArgumentNullException(nameof(startupAssemblyName));
+            }
+
+
+            return hostBuilder
+                .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
+                .UseSetting(WebHostDefaults.StartupAssemblyKey, startupAssemblyName);
+        }
+
+        /// <summary>
+        /// Specify the server to be used by the web host.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
+        /// <param name="server">The <see cref="IServer"/> to be used.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder UseServer(this IWebHostBuilder hostBuilder, IServer server)
+        {
+            if (server == null)
+            {
+                throw new ArgumentNullException(nameof(server));
+            }
+
+            return hostBuilder.ConfigureServices(services =>
+            {
+                // It would be nicer if this was transient but we need to pass in the
+                // factory instance directly
+                services.AddSingleton(server);
+            });
+        }
+
+        /// <summary>
+        /// Specify the environment to be used by the web host.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
+        /// <param name="environment">The environment to host the application in.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder UseEnvironment(this IWebHostBuilder hostBuilder, string environment)
+        {
+            if (environment == null)
+            {
+                throw new ArgumentNullException(nameof(environment));
+            }
+
+            return hostBuilder.UseSetting(WebHostDefaults.EnvironmentKey, environment);
+        }
+
+        /// <summary>
+        /// Specify the content root directory to be used by the web host.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
+        /// <param name="contentRoot">Path to root directory of the application.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder UseContentRoot(this IWebHostBuilder hostBuilder, string contentRoot)
+        {
+            if (contentRoot == null)
+            {
+                throw new ArgumentNullException(nameof(contentRoot));
+            }
+
+            return hostBuilder.UseSetting(WebHostDefaults.ContentRootKey, contentRoot);
+        }
+
+        /// <summary>
+        /// Specify the webroot directory to be used by the web host.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
+        /// <param name="webRoot">Path to the root directory used by the web server.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder UseWebRoot(this IWebHostBuilder hostBuilder, string webRoot)
+        {
+            if (webRoot == null)
+            {
+                throw new ArgumentNullException(nameof(webRoot));
+            }
+
+            return hostBuilder.UseSetting(WebHostDefaults.WebRootKey, webRoot);
+        }
+
+        /// <summary>
+        /// Specify the urls the web host will listen on.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
+        /// <param name="urls">The urls the hosted application will listen on.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder UseUrls(this IWebHostBuilder hostBuilder, params string[] urls)
+        {
+            if (urls == null)
+            {
+                throw new ArgumentNullException(nameof(urls));
+            }
+
+            return hostBuilder.UseSetting(WebHostDefaults.ServerUrlsKey, string.Join(ServerUrlsSeparator, urls));
+        }
+
+        /// <summary>
+        /// Indicate whether the host should listen on the URLs configured on the <see cref="IWebHostBuilder"/>
+        /// instead of those configured on the <see cref="IServer"/>.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
+        /// <param name="preferHostingUrls"><c>true</c> to prefer URLs configured on the <see cref="IWebHostBuilder"/>; otherwise <c>false</c>.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder PreferHostingUrls(this IWebHostBuilder hostBuilder, bool preferHostingUrls)
+        {
+            return hostBuilder.UseSetting(WebHostDefaults.PreferHostingUrlsKey, preferHostingUrls ? "true" : "false");
+        }
+
+        /// <summary>
+        /// Specify if startup status messages should be suppressed.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
+        /// <param name="suppressStatusMessages"><c>true</c> to suppress writing of hosting startup status messages; otherwise <c>false</c>.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder SuppressStatusMessages(this IWebHostBuilder hostBuilder, bool suppressStatusMessages)
+        {
+            return hostBuilder.UseSetting(WebHostDefaults.SuppressStatusMessagesKey, suppressStatusMessages ? "true" : "false");
+        }
+
+        /// <summary>
+        /// Specify the amount of time to wait for the web host to shutdown.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
+        /// <param name="timeout">The amount of time to wait for server shutdown.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder UseShutdownTimeout(this IWebHostBuilder hostBuilder, TimeSpan timeout)
+        {
+            return hostBuilder.UseSetting(WebHostDefaults.ShutdownTimeoutKey, ((int)timeout.TotalSeconds).ToString(CultureInfo.InvariantCulture));
+        }
+
+        /// <summary>
+        /// Start the web host and listen on the specified urls.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to start.</param>
+        /// <param name="urls">The urls the hosted application will listen on.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHost Start(this IWebHostBuilder hostBuilder, params string[] urls)
+        {
+            var host = hostBuilder.UseUrls(urls).Build();
+            host.StartAsync(CancellationToken.None).GetAwaiter().GetResult();
+            return host;
+        }
+    }
+}
diff --git a/src/Hosting/Abstractions/src/HostingEnvironmentExtensions.cs b/src/Hosting/Abstractions/src/HostingEnvironmentExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ad3269859da579727c55f9d9c23dfb364d5667da
--- /dev/null
+++ b/src/Hosting/Abstractions/src/HostingEnvironmentExtensions.cs
@@ -0,0 +1,80 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    /// <summary>
+    /// Extension methods for <see cref="IHostingEnvironment"/>.
+    /// </summary>
+    public static class HostingEnvironmentExtensions
+    {
+        /// <summary>
+        /// Checks if the current hosting environment name is <see cref="EnvironmentName.Development"/>.
+        /// </summary>
+        /// <param name="hostingEnvironment">An instance of <see cref="IHostingEnvironment"/>.</param>
+        /// <returns>True if the environment name is <see cref="EnvironmentName.Development"/>, otherwise false.</returns>
+        public static bool IsDevelopment(this IHostingEnvironment hostingEnvironment)
+        {
+            if (hostingEnvironment == null)
+            {
+                throw new ArgumentNullException(nameof(hostingEnvironment));
+            }
+
+            return hostingEnvironment.IsEnvironment(EnvironmentName.Development);
+        }
+
+        /// <summary>
+        /// Checks if the current hosting environment name is <see cref="EnvironmentName.Staging"/>.
+        /// </summary>
+        /// <param name="hostingEnvironment">An instance of <see cref="IHostingEnvironment"/>.</param>
+        /// <returns>True if the environment name is <see cref="EnvironmentName.Staging"/>, otherwise false.</returns>
+        public static bool IsStaging(this IHostingEnvironment hostingEnvironment)
+        {
+            if (hostingEnvironment == null)
+            {
+                throw new ArgumentNullException(nameof(hostingEnvironment));
+            }
+
+            return hostingEnvironment.IsEnvironment(EnvironmentName.Staging);
+        }
+
+        /// <summary>
+        /// Checks if the current hosting environment name is <see cref="EnvironmentName.Production"/>.
+        /// </summary>
+        /// <param name="hostingEnvironment">An instance of <see cref="IHostingEnvironment"/>.</param>
+        /// <returns>True if the environment name is <see cref="EnvironmentName.Production"/>, otherwise false.</returns>
+        public static bool IsProduction(this IHostingEnvironment hostingEnvironment)
+        {
+            if (hostingEnvironment == null)
+            {
+                throw new ArgumentNullException(nameof(hostingEnvironment));
+            }
+
+            return hostingEnvironment.IsEnvironment(EnvironmentName.Production);
+        }
+
+        /// <summary>
+        /// Compares the current hosting environment name against the specified value.
+        /// </summary>
+        /// <param name="hostingEnvironment">An instance of <see cref="IHostingEnvironment"/>.</param>
+        /// <param name="environmentName">Environment name to validate against.</param>
+        /// <returns>True if the specified name is the same as the current environment, otherwise false.</returns>
+        public static bool IsEnvironment(
+            this IHostingEnvironment hostingEnvironment,
+            string environmentName)
+        {
+            if (hostingEnvironment == null)
+            {
+                throw new ArgumentNullException(nameof(hostingEnvironment));
+            }
+
+            return string.Equals(
+                hostingEnvironment.EnvironmentName,
+                environmentName,
+                StringComparison.OrdinalIgnoreCase);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Abstractions/src/HostingStartupAttribute.cs b/src/Hosting/Abstractions/src/HostingStartupAttribute.cs
new file mode 100644
index 0000000000000000000000000000000000000000..cb028c327b5c030b38a7632301a7f37acd5f96d9
--- /dev/null
+++ b/src/Hosting/Abstractions/src/HostingStartupAttribute.cs
@@ -0,0 +1,40 @@
+// 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.Reflection;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    /// <summary>
+    /// Marker attribute indicating an implementation of <see cref="IHostingStartup"/> that will be loaded and executed when building an <see cref="IWebHost"/>.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Assembly, Inherited = false, AllowMultiple = true)]
+    public sealed class HostingStartupAttribute : Attribute
+    {
+        /// <summary>
+        /// Constructs the <see cref="HostingStartupAttribute"/> with the specified type.
+        /// </summary>
+        /// <param name="hostingStartupType">A type that implements <see cref="IHostingStartup"/>.</param>
+        public HostingStartupAttribute(Type hostingStartupType)
+        {
+            if (hostingStartupType == null)
+            {
+                throw new ArgumentNullException(nameof(hostingStartupType));
+            }
+
+            if (!typeof(IHostingStartup).GetTypeInfo().IsAssignableFrom(hostingStartupType.GetTypeInfo()))
+            {
+                throw new ArgumentException($@"""{hostingStartupType}"" does not implement {typeof(IHostingStartup)}.", nameof(hostingStartupType));
+            }
+
+            HostingStartupType = hostingStartupType;
+        }
+
+        /// <summary>
+        /// The implementation of <see cref="IHostingStartup"/> that should be loaded when 
+        /// starting an application.
+        /// </summary>
+        public Type HostingStartupType { get; }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Abstractions/src/IApplicationLifetime.cs b/src/Hosting/Abstractions/src/IApplicationLifetime.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f4613dd7d921d8da1b6627c9cbc3c588fa64aad1
--- /dev/null
+++ b/src/Hosting/Abstractions/src/IApplicationLifetime.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;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    /// <summary>
+    /// Allows consumers to perform cleanup during a graceful shutdown.
+    /// </summary>
+    public interface IApplicationLifetime
+    {
+        /// <summary>
+        /// Triggered when the application host has fully started and is about to wait
+        /// for a graceful shutdown.
+        /// </summary>
+        CancellationToken ApplicationStarted { get; }
+
+        /// <summary>
+        /// Triggered when the application host is performing a graceful shutdown.
+        /// Requests may still be in flight. Shutdown will block until this event completes.
+        /// </summary>
+        CancellationToken ApplicationStopping { get; }
+
+        /// <summary>
+        /// Triggered when the application host is performing a graceful shutdown.
+        /// All requests should be complete at this point. Shutdown will block
+        /// until this event completes.
+        /// </summary>
+        CancellationToken ApplicationStopped { get; }
+
+        /// <summary>
+        /// Requests termination of the current application.
+        /// </summary>
+        void StopApplication();
+    }
+}
diff --git a/src/Hosting/Abstractions/src/IHostingEnvironment.cs b/src/Hosting/Abstractions/src/IHostingEnvironment.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5feeb38eb7e65f1c775a6e3c770f752b4fca6644
--- /dev/null
+++ b/src/Hosting/Abstractions/src/IHostingEnvironment.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.FileProviders;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    /// <summary>
+    /// Provides information about the web hosting environment an application is running in.
+    /// </summary>
+    public interface IHostingEnvironment
+    {
+        /// <summary>
+        /// Gets or sets the name of the environment. The host automatically sets this property to the value
+        /// of the "ASPNETCORE_ENVIRONMENT" environment variable, or "environment" as specified in any other configuration source.
+        /// </summary>
+        string EnvironmentName { get; set; }
+
+        /// <summary>
+        /// Gets or sets the name of the application. This property is automatically set by the host to the assembly containing
+        /// the application entry point.
+        /// </summary>
+        string ApplicationName { get; set; }
+
+        /// <summary>
+        /// Gets or sets the absolute path to the directory that contains the web-servable application content files.
+        /// </summary>
+        string WebRootPath { get; set; }
+
+        /// <summary>
+        /// Gets or sets an <see cref="IFileProvider"/> pointing at <see cref="WebRootPath"/>.
+        /// </summary>
+        IFileProvider WebRootFileProvider { get; set; }
+
+        /// <summary>
+        /// Gets or sets the absolute path to the directory that contains the application content files.
+        /// </summary>
+        string ContentRootPath { get; set; }
+
+        /// <summary>
+        /// Gets or sets an <see cref="IFileProvider"/> pointing at <see cref="ContentRootPath"/>.
+        /// </summary>
+        IFileProvider ContentRootFileProvider { get; set; }
+    }
+}
diff --git a/src/Hosting/Abstractions/src/IHostingStartup.cs b/src/Hosting/Abstractions/src/IHostingStartup.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e65ed18fb63fe6ef2852e255d7f81e5cc0ccd341
--- /dev/null
+++ b/src/Hosting/Abstractions/src/IHostingStartup.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 System;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    /// <summary>
+    /// Represents platform specific configuration that will be applied to a <see cref="IWebHostBuilder"/> when building an <see cref="IWebHost"/>.
+    /// </summary>
+    public interface IHostingStartup
+    {
+        /// <summary>
+        /// Configure the <see cref="IWebHostBuilder"/>.
+        /// </summary>
+        /// <remarks>
+        /// Configure is intended to be called before user code, allowing a user to overwrite any changes made.
+        /// </remarks>
+        /// <param name="builder"></param>
+        void Configure(IWebHostBuilder builder);
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Abstractions/src/IStartup.cs b/src/Hosting/Abstractions/src/IStartup.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3a533c8df2a7da5e1699ceb7921f0bbadc668d51
--- /dev/null
+++ b/src/Hosting/Abstractions/src/IStartup.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    public interface IStartup
+    {
+        IServiceProvider ConfigureServices(IServiceCollection services);
+
+        void Configure(IApplicationBuilder app);
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Abstractions/src/IStartupFilter.cs b/src/Hosting/Abstractions/src/IStartupFilter.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2f0a3cf39d3adfd09c13cc119a13871b4aba8d59
--- /dev/null
+++ b/src/Hosting/Abstractions/src/IStartupFilter.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.
+
+using System;
+using Microsoft.AspNetCore.Builder;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    public interface IStartupFilter
+    {
+        Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next);
+    }
+}
diff --git a/src/Hosting/Abstractions/src/IWebHost.cs b/src/Hosting/Abstractions/src/IWebHost.cs
new file mode 100644
index 0000000000000000000000000000000000000000..97331e47680bdf6ea94ddf72d1e4b9dd4db9b9e5
--- /dev/null
+++ b/src/Hosting/Abstractions/src/IWebHost.cs
@@ -0,0 +1,43 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    /// <summary>
+    /// Represents a configured web host.
+    /// </summary>
+    public interface IWebHost : IDisposable
+    {
+        /// <summary>
+        /// The <see cref="IFeatureCollection"/> exposed by the configured server.
+        /// </summary>
+        IFeatureCollection ServerFeatures { get; }
+
+        /// <summary>
+        /// The <see cref="IServiceProvider"/> for the host.
+        /// </summary>
+        IServiceProvider Services { get; }
+
+        /// <summary>
+        /// Starts listening on the configured addresses.
+        /// </summary>
+        void Start();
+
+        /// <summary>
+        /// Starts listening on the configured addresses.
+        /// </summary>
+        Task StartAsync(CancellationToken cancellationToken = default);
+
+        /// <summary>
+        /// Attempt to gracefully stop the host.
+        /// </summary>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task StopAsync(CancellationToken cancellationToken = default);
+    }
+}
diff --git a/src/Hosting/Abstractions/src/IWebHostBuilder.cs b/src/Hosting/Abstractions/src/IWebHostBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2cf3bc116327d7037cff6ddbc600429410b4d937
--- /dev/null
+++ b/src/Hosting/Abstractions/src/IWebHostBuilder.cs
@@ -0,0 +1,63 @@
+// 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.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    /// <summary>
+    /// A builder for <see cref="IWebHost"/>.
+    /// </summary>
+    public interface IWebHostBuilder
+    {
+        /// <summary>
+        /// Builds an <see cref="IWebHost"/> which hosts a web application.
+        /// </summary>
+        IWebHost Build();
+
+        /// <summary>
+        /// Adds a delegate for configuring the <see cref="IConfigurationBuilder"/> that will construct an <see cref="IConfiguration"/>.
+        /// </summary>
+        /// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder" /> that will be used to construct an <see cref="IConfiguration" />.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        /// <remarks>
+        /// The <see cref="IConfiguration"/> and <see cref="ILoggerFactory"/> on the <see cref="WebHostBuilderContext"/> are uninitialized at this stage.
+        /// The <see cref="IConfigurationBuilder"/> is pre-populated with the settings of the <see cref="IWebHostBuilder"/>.
+        /// </remarks>
+        IWebHostBuilder ConfigureAppConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate);
+
+        /// <summary>
+        /// Adds a delegate for configuring additional services for the host or web application. This may be called
+        /// multiple times.
+        /// </summary>
+        /// <param name="configureServices">A delegate for configuring the <see cref="IServiceCollection"/>.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices);
+
+        /// <summary>
+        /// Adds a delegate for configuring additional services for the host or web application. This may be called
+        /// multiple times.
+        /// </summary>
+        /// <param name="configureServices">A delegate for configuring the <see cref="IServiceCollection"/>.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices);
+
+        /// <summary>
+        /// Get the setting value from the configuration.
+        /// </summary>
+        /// <param name="key">The key of the setting to look up.</param>
+        /// <returns>The value the setting currently contains.</returns>
+        string GetSetting(string key);
+
+        /// <summary>
+        /// Add or replace a setting in the configuration.
+        /// </summary>
+        /// <param name="key">The key of the setting to add or replace.</param>
+        /// <param name="value">The value of the setting to add or replace.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        IWebHostBuilder UseSetting(string key, string value);
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Abstractions/src/Internal/IStartupConfigureContainerFilter.cs b/src/Hosting/Abstractions/src/Internal/IStartupConfigureContainerFilter.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e58ac19774deae4f5715bb5d91ea278660fd55b2
--- /dev/null
+++ b/src/Hosting/Abstractions/src/Internal/IStartupConfigureContainerFilter.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    /// <summary>
+    /// This API supports the ASP.NET Core infrastructure and is not intended to be used
+    /// directly from your code. This API may change or be removed in future releases.
+    /// </summary>
+    public interface IStartupConfigureContainerFilter<TContainerBuilder>
+    {
+        Action<TContainerBuilder> ConfigureContainer(Action<TContainerBuilder> container);
+    }
+}
diff --git a/src/Hosting/Abstractions/src/Internal/IStartupConfigureServicesFilter.cs b/src/Hosting/Abstractions/src/Internal/IStartupConfigureServicesFilter.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ad203bcedb6078fe0197290722461f541de4af1f
--- /dev/null
+++ b/src/Hosting/Abstractions/src/Internal/IStartupConfigureServicesFilter.cs
@@ -0,0 +1,17 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    /// <summary>
+    /// This API supports the ASP.NET Core infrastructure and is not intended to be used
+    /// directly from your code. This API may change or be removed in future releases.
+    /// </summary>
+    public interface IStartupConfigureServicesFilter
+    {
+        Action<IServiceCollection> ConfigureServices(Action<IServiceCollection> next);
+    }
+}
diff --git a/src/Hosting/Abstractions/src/Microsoft.AspNetCore.Hosting.Abstractions.csproj b/src/Hosting/Abstractions/src/Microsoft.AspNetCore.Hosting.Abstractions.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..a01be8ea3f16085774adeee2d12bf35d9ae5c533
--- /dev/null
+++ b/src/Hosting/Abstractions/src/Microsoft.AspNetCore.Hosting.Abstractions.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>ASP.NET Core hosting and startup abstractions for web applications.</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <NoWarn>$(NoWarn);CS1591</NoWarn>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore;hosting</PackageTags>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" />
+    <Reference Include="Microsoft.AspNetCore.Http.Abstractions" />
+    <Reference Include="Microsoft.Extensions.Hosting.Abstractions" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Hosting/Abstractions/src/WebHostBuilderContext.cs b/src/Hosting/Abstractions/src/WebHostBuilderContext.cs
new file mode 100644
index 0000000000000000000000000000000000000000..58e8d0798b0b37870b1f08c1b84283fd23ff13e2
--- /dev/null
+++ b/src/Hosting/Abstractions/src/WebHostBuilderContext.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.Extensions.Configuration;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    /// <summary>
+    /// Context containing the common services on the <see cref="IWebHost" />. Some properties may be null until set by the <see cref="IWebHost" />.
+    /// </summary>
+    public class WebHostBuilderContext
+    {
+        /// <summary>
+        /// The <see cref="IHostingEnvironment" /> initialized by the <see cref="IWebHost" />.
+        /// </summary>
+        public IHostingEnvironment HostingEnvironment { get; set; }
+
+        /// <summary>
+        /// The <see cref="IConfiguration" /> containing the merged configuration of the application and the <see cref="IWebHost" />.
+        /// </summary>
+        public IConfiguration Configuration { get; set; }
+    }
+}
diff --git a/src/Hosting/Abstractions/src/WebHostDefaults.cs b/src/Hosting/Abstractions/src/WebHostDefaults.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4de391d0a28b9910963adc009bcaa833fabaa9b4
--- /dev/null
+++ b/src/Hosting/Abstractions/src/WebHostDefaults.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.
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    public static class WebHostDefaults
+    {
+        public static readonly string ApplicationKey = "applicationName";
+        public static readonly string StartupAssemblyKey = "startupAssembly";
+        public static readonly string HostingStartupAssembliesKey = "hostingStartupAssemblies";
+        public static readonly string HostingStartupExcludeAssembliesKey = "hostingStartupExcludeAssemblies";
+
+        public static readonly string DetailedErrorsKey = "detailedErrors";
+        public static readonly string EnvironmentKey = "environment";
+        public static readonly string WebRootKey = "webroot";
+        public static readonly string CaptureStartupErrorsKey = "captureStartupErrors";
+        public static readonly string ServerUrlsKey = "urls";
+        public static readonly string ContentRootKey = "contentRoot";
+        public static readonly string PreferHostingUrlsKey = "preferHostingUrls";
+        public static readonly string PreventHostingStartupKey = "preventHostingStartup";
+        public static readonly string SuppressStatusMessagesKey = "suppressStatusMessages";
+
+        public static readonly string ShutdownTimeoutKey = "shutdownTimeoutSeconds";
+    }
+}
diff --git a/src/Hosting/Abstractions/src/baseline.netcore.json b/src/Hosting/Abstractions/src/baseline.netcore.json
new file mode 100644
index 0000000000000000000000000000000000000000..7536bf12332e960616fd6ca8f4a9884ff3ec4964
--- /dev/null
+++ b/src/Hosting/Abstractions/src/baseline.netcore.json
@@ -0,0 +1,947 @@
+{
+  "AssemblyIdentity": "Microsoft.AspNetCore.Hosting.Abstractions, Version=2.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+  "Types": [
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.EnvironmentName",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Field",
+          "Name": "Development",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Staging",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Production",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.HostingAbstractionsWebHostBuilderExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "UseConfiguration",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "configuration",
+              "Type": "Microsoft.Extensions.Configuration.IConfiguration"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "CaptureStartupErrors",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "captureStartupErrors",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseStartup",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "startupAssemblyName",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseServer",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "server",
+              "Type": "Microsoft.AspNetCore.Hosting.Server.IServer"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseEnvironment",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "environment",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseContentRoot",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "contentRoot",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseWebRoot",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "webRoot",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseUrls",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "urls",
+              "Type": "System.String[]",
+              "IsParams": true
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "PreferHostingUrls",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "preferHostingUrls",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseShutdownTimeout",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "timeout",
+              "Type": "System.TimeSpan"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Start",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "urls",
+              "Type": "System.String[]",
+              "IsParams": true
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHost",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.HostingEnvironmentExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "IsDevelopment",
+          "Parameters": [
+            {
+              "Name": "hostingEnvironment",
+              "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "IsStaging",
+          "Parameters": [
+            {
+              "Name": "hostingEnvironment",
+              "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "IsProduction",
+          "Parameters": [
+            {
+              "Name": "hostingEnvironment",
+              "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "IsEnvironment",
+          "Parameters": [
+            {
+              "Name": "hostingEnvironment",
+              "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment"
+            },
+            {
+              "Name": "environmentName",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.HostingStartupAttribute",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Sealed": true,
+      "BaseType": "System.Attribute",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_HostingStartupType",
+          "Parameters": [],
+          "ReturnType": "System.Type",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "hostingStartupType",
+              "Type": "System.Type"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.IApplicationLifetime",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_ApplicationStarted",
+          "Parameters": [],
+          "ReturnType": "System.Threading.CancellationToken",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ApplicationStopping",
+          "Parameters": [],
+          "ReturnType": "System.Threading.CancellationToken",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ApplicationStopped",
+          "Parameters": [],
+          "ReturnType": "System.Threading.CancellationToken",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "StopApplication",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.IHostingEnvironment",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_EnvironmentName",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_EnvironmentName",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ApplicationName",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ApplicationName",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_WebRootPath",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_WebRootPath",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_WebRootFileProvider",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.FileProviders.IFileProvider",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_WebRootFileProvider",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.FileProviders.IFileProvider"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentRootPath",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentRootPath",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentRootFileProvider",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.FileProviders.IFileProvider",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentRootFileProvider",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.FileProviders.IFileProvider"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.IHostingStartup",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Configure",
+          "Parameters": [
+            {
+              "Name": "builder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.IStartup",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "ConfigureServices",
+          "Parameters": [
+            {
+              "Name": "services",
+              "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
+            }
+          ],
+          "ReturnType": "System.IServiceProvider",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Configure",
+          "Parameters": [
+            {
+              "Name": "app",
+              "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.IStartupFilter",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Configure",
+          "Parameters": [
+            {
+              "Name": "next",
+              "Type": "System.Action<Microsoft.AspNetCore.Builder.IApplicationBuilder>"
+            }
+          ],
+          "ReturnType": "System.Action<Microsoft.AspNetCore.Builder.IApplicationBuilder>",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.IWebHost",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [
+        "System.IDisposable"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_ServerFeatures",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Services",
+          "Parameters": [],
+          "ReturnType": "System.IServiceProvider",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Start",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "StartAsync",
+          "Parameters": [
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "StopAsync",
+          "Parameters": [
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Build",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHost",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ConfigureAppConfiguration",
+          "Parameters": [
+            {
+              "Name": "configureDelegate",
+              "Type": "System.Action<Microsoft.AspNetCore.Hosting.WebHostBuilderContext, Microsoft.Extensions.Configuration.IConfigurationBuilder>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ConfigureServices",
+          "Parameters": [
+            {
+              "Name": "configureServices",
+              "Type": "System.Action<Microsoft.Extensions.DependencyInjection.IServiceCollection>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ConfigureServices",
+          "Parameters": [
+            {
+              "Name": "configureServices",
+              "Type": "System.Action<Microsoft.AspNetCore.Hosting.WebHostBuilderContext, Microsoft.Extensions.DependencyInjection.IServiceCollection>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetSetting",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseSetting",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.WebHostBuilderContext",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_HostingEnvironment",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IHostingEnvironment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_HostingEnvironment",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Configuration",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Configuration.IConfiguration",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Configuration",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Configuration.IConfiguration"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.WebHostDefaults",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Field",
+          "Name": "ApplicationKey",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "StartupAssemblyKey",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "HostingStartupAssembliesKey",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "DetailedErrorsKey",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "EnvironmentKey",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "WebRootKey",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "CaptureStartupErrorsKey",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "ServerUrlsKey",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "ContentRootKey",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "PreferHostingUrlsKey",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "PreventHostingStartupKey",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "ShutdownTimeoutKey",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/Builder/ApplicationBuilderFactory.cs b/src/Hosting/Hosting/src/Builder/ApplicationBuilderFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e188c0b7fd25c7df2987ec6d80b5c7ad15873373
--- /dev/null
+++ b/src/Hosting/Hosting/src/Builder/ApplicationBuilderFactory.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 Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Builder.Internal;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Hosting.Builder
+{
+    public class ApplicationBuilderFactory : IApplicationBuilderFactory
+    {
+        private readonly IServiceProvider _serviceProvider;
+
+        public ApplicationBuilderFactory(IServiceProvider serviceProvider)
+        {
+            _serviceProvider = serviceProvider;
+        }
+
+        public IApplicationBuilder CreateBuilder(IFeatureCollection serverFeatures)
+        {
+            return new ApplicationBuilder(_serviceProvider, serverFeatures);
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/src/Builder/IApplicationBuilderFactory.cs b/src/Hosting/Hosting/src/Builder/IApplicationBuilderFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d44398fb69d89fefe98c1e20716d0cbb2343037b
--- /dev/null
+++ b/src/Hosting/Hosting/src/Builder/IApplicationBuilderFactory.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.
+
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Hosting.Builder
+{
+    public interface IApplicationBuilderFactory
+    {
+        IApplicationBuilder CreateBuilder(IFeatureCollection serverFeatures);
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/Internal/ApplicationLifetime.cs b/src/Hosting/Hosting/src/Internal/ApplicationLifetime.cs
new file mode 100644
index 0000000000000000000000000000000000000000..958f8b5dcc1d339ca80d998d2d99d85f3f83c6fc
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/ApplicationLifetime.cs
@@ -0,0 +1,114 @@
+// 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;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    /// <summary>
+    /// Allows consumers to perform cleanup during a graceful shutdown.
+    /// </summary>
+    public class ApplicationLifetime : IApplicationLifetime, Extensions.Hosting.IApplicationLifetime
+    {
+        private readonly CancellationTokenSource _startedSource = new CancellationTokenSource();
+        private readonly CancellationTokenSource _stoppingSource = new CancellationTokenSource();
+        private readonly CancellationTokenSource _stoppedSource = new CancellationTokenSource();
+        private readonly ILogger<ApplicationLifetime> _logger;
+
+        public ApplicationLifetime(ILogger<ApplicationLifetime> logger)
+        {
+            _logger = logger;
+        }
+
+        /// <summary>
+        /// Triggered when the application host has fully started and is about to wait
+        /// for a graceful shutdown.
+        /// </summary>
+        public CancellationToken ApplicationStarted => _startedSource.Token;
+
+        /// <summary>
+        /// Triggered when the application host is performing a graceful shutdown.
+        /// Request may still be in flight. Shutdown will block until this event completes.
+        /// </summary>
+        public CancellationToken ApplicationStopping => _stoppingSource.Token;
+
+        /// <summary>
+        /// Triggered when the application host is performing a graceful shutdown.
+        /// All requests should be complete at this point. Shutdown will block
+        /// until this event completes.
+        /// </summary>
+        public CancellationToken ApplicationStopped => _stoppedSource.Token;
+
+        /// <summary>
+        /// Signals the ApplicationStopping event and blocks until it completes.
+        /// </summary>
+        public void StopApplication()
+        {
+            // Lock on CTS to synchronize multiple calls to StopApplication. This guarantees that the first call 
+            // to StopApplication and its callbacks run to completion before subsequent calls to StopApplication, 
+            // which will no-op since the first call already requested cancellation, get a chance to execute.
+            lock (_stoppingSource)
+            {
+                try
+                {
+                    ExecuteHandlers(_stoppingSource);
+                }
+                catch (Exception ex)
+                {
+                    _logger.ApplicationError(LoggerEventIds.ApplicationStoppingException,
+                                             "An error occurred stopping the application",
+                                             ex);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Signals the ApplicationStarted event and blocks until it completes.
+        /// </summary>
+        public void NotifyStarted()
+        {
+            try
+            {
+                ExecuteHandlers(_startedSource);
+            }
+            catch (Exception ex)
+            {
+                _logger.ApplicationError(LoggerEventIds.ApplicationStartupException,
+                                         "An error occurred starting the application",
+                                         ex);
+            }
+        }
+
+        /// <summary>
+        /// Signals the ApplicationStopped event and blocks until it completes.
+        /// </summary>
+        public void NotifyStopped()
+        {
+            try
+            {
+                ExecuteHandlers(_stoppedSource);
+            }
+            catch (Exception ex)
+            {
+                _logger.ApplicationError(LoggerEventIds.ApplicationStoppedException,
+                                         "An error occurred stopping the application",
+                                         ex);
+            }
+        }
+
+        private void ExecuteHandlers(CancellationTokenSource cancel)
+        {
+            // Noop if this is already cancelled
+            if (cancel.IsCancellationRequested)
+            {
+                return;
+            }
+
+            // Run the cancellation token callbacks
+            cancel.Cancel(throwOnFirstException: false);
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/src/Internal/AutoRequestServicesStartupFilter.cs b/src/Hosting/Hosting/src/Internal/AutoRequestServicesStartupFilter.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b75958fa52cf54074adcd7efbbb0ae7bafb5b0f8
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/AutoRequestServicesStartupFilter.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 System;
+using Microsoft.AspNetCore.Builder;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    public class AutoRequestServicesStartupFilter : IStartupFilter
+    {
+        public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
+        {
+            return builder =>
+            {
+                builder.UseMiddleware<RequestServicesContainerMiddleware>();
+                next(builder);
+            };
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/src/Internal/ConfigureBuilder.cs b/src/Hosting/Hosting/src/Internal/ConfigureBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..37b715c5b04e1e534c5ad8561136932130af39f3
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/ConfigureBuilder.cs
@@ -0,0 +1,59 @@
+// 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.Reflection;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    public class ConfigureBuilder
+    {
+        public ConfigureBuilder(MethodInfo configure)
+        {
+            MethodInfo = configure;
+        }
+
+        public MethodInfo MethodInfo { get; }
+
+        public Action<IApplicationBuilder> Build(object instance) => builder => Invoke(instance, builder);
+
+        private void Invoke(object instance, IApplicationBuilder builder)
+        {
+            // Create a scope for Configure, this allows creating scoped dependencies
+            // without the hassle of manually creating a scope.
+            using (var scope = builder.ApplicationServices.CreateScope())
+            {
+                var serviceProvider = scope.ServiceProvider;
+                var parameterInfos = MethodInfo.GetParameters();
+                var parameters = new object[parameterInfos.Length];
+                for (var index = 0; index < parameterInfos.Length; index++)
+                {
+                    var parameterInfo = parameterInfos[index];
+                    if (parameterInfo.ParameterType == typeof(IApplicationBuilder))
+                    {
+                        parameters[index] = builder;
+                    }
+                    else
+                    {
+                        try
+                        {
+                            parameters[index] = serviceProvider.GetRequiredService(parameterInfo.ParameterType);
+                        }
+                        catch (Exception ex)
+                        {
+                            throw new Exception(string.Format(
+                                "Could not resolve a service of type '{0}' for the parameter '{1}' of method '{2}' on type '{3}'.",
+                                parameterInfo.ParameterType.FullName,
+                                parameterInfo.Name,
+                                MethodInfo.Name,
+                                MethodInfo.DeclaringType.FullName), ex);
+                        }
+                    }
+                }
+                MethodInfo.Invoke(instance, parameters);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/Internal/ConfigureContainerBuilder.cs b/src/Hosting/Hosting/src/Internal/ConfigureContainerBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ed8d0fd06e842b4e6f7c914c2add13df20fdf62c
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/ConfigureContainerBuilder.cs
@@ -0,0 +1,52 @@
+// 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.Reflection;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    public class ConfigureContainerBuilder
+    {
+        public ConfigureContainerBuilder(MethodInfo configureContainerMethod)
+        {
+            MethodInfo = configureContainerMethod;
+        }
+
+        public MethodInfo MethodInfo { get; }
+
+        public Func<Action<object>, Action<object>> ConfigureContainerFilters { get; set; }
+
+        public Action<object> Build(object instance) => container => Invoke(instance, container);
+
+        public Type GetContainerType()
+        {
+            var parameters = MethodInfo.GetParameters();
+            if (parameters.Length != 1)
+            {
+                // REVIEW: This might be a breaking change
+                throw new InvalidOperationException($"The {MethodInfo.Name} method must take only one parameter.");
+            }
+            return parameters[0].ParameterType;
+        }
+
+        private void Invoke(object instance, object container)
+        {
+            ConfigureContainerFilters(StartupConfigureContainer)(container);
+
+            void StartupConfigureContainer(object containerBuilder) => InvokeCore(instance, containerBuilder);
+        }
+
+        private void InvokeCore(object instance, object container)
+        {
+            if (MethodInfo == null)
+            {
+                return;
+            }
+
+            var arguments = new object[1] { container };
+
+            MethodInfo.Invoke(instance, arguments);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/Internal/ConfigureServicesBuilder.cs b/src/Hosting/Hosting/src/Internal/ConfigureServicesBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4206d0d62a0bac431acb2e5df3766cfd2c33cd21
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/ConfigureServicesBuilder.cs
@@ -0,0 +1,56 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.Reflection;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    public class ConfigureServicesBuilder
+    {
+        public ConfigureServicesBuilder(MethodInfo configureServices)
+        {
+            MethodInfo = configureServices;
+        }
+
+        public MethodInfo MethodInfo { get; }
+
+        public Func<Func<IServiceCollection, IServiceProvider>, Func<IServiceCollection, IServiceProvider>> StartupServiceFilters { get; set; }
+
+        public Func<IServiceCollection, IServiceProvider> Build(object instance) => services => Invoke(instance, services);
+
+        private IServiceProvider Invoke(object instance, IServiceCollection services)
+        {
+            return StartupServiceFilters(Startup)(services);
+
+            IServiceProvider Startup(IServiceCollection serviceCollection) => InvokeCore(instance, serviceCollection);
+        }
+
+        private IServiceProvider InvokeCore(object instance, IServiceCollection services)
+        {
+            if (MethodInfo == null)
+            {
+                return null;
+            }
+
+            // Only support IServiceCollection parameters
+            var parameters = MethodInfo.GetParameters();
+            if (parameters.Length > 1 ||
+                parameters.Any(p => p.ParameterType != typeof(IServiceCollection)))
+            {
+                throw new InvalidOperationException("The ConfigureServices method must either be parameterless or take only one parameter of type IServiceCollection.");
+            }
+
+            var arguments = new object[MethodInfo.GetParameters().Length];
+
+            if (parameters.Length > 0)
+            {
+                arguments[0] = services;
+            }
+
+            return MethodInfo.Invoke(instance, arguments) as IServiceProvider;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/Internal/HostedServiceExecutor.cs b/src/Hosting/Hosting/src/Internal/HostedServiceExecutor.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ee6fbcfad8aba1d06dd1af45f8b7013db4ddcd9e
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/HostedServiceExecutor.cs
@@ -0,0 +1,76 @@
+// 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;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    public class HostedServiceExecutor
+    {
+        private readonly IEnumerable<IHostedService> _services;
+        private readonly ILogger<HostedServiceExecutor> _logger;
+
+        public HostedServiceExecutor(ILogger<HostedServiceExecutor> logger, IEnumerable<IHostedService> services)
+        {
+            _logger = logger;
+            _services = services;
+        }
+
+        public async Task StartAsync(CancellationToken token)
+        {
+            try
+            {
+                await ExecuteAsync(service => service.StartAsync(token));
+            }
+            catch (Exception ex)
+            {
+                _logger.ApplicationError(LoggerEventIds.HostedServiceStartException, "An error occurred starting the application", ex);
+            }
+        }
+
+        public async Task StopAsync(CancellationToken token)
+        {
+            try
+            {
+                await ExecuteAsync(service => service.StopAsync(token));
+            }
+            catch (Exception ex)
+            {
+                _logger.ApplicationError(LoggerEventIds.HostedServiceStopException, "An error occurred stopping the application", ex);
+            }
+        }
+
+        private async Task ExecuteAsync(Func<IHostedService, Task> callback)
+        {
+            List<Exception> exceptions = null;
+
+            foreach (var service in _services)
+            {
+                try
+                {
+                    await callback(service);
+                }
+                catch (Exception ex)
+                {
+                    if (exceptions == null)
+                    {
+                        exceptions = new List<Exception>();
+                    }
+
+                    exceptions.Add(ex);
+                }
+            }
+
+            // Throw an aggregate exception if there were any exceptions
+            if (exceptions != null)
+            {
+                throw new AggregateException(exceptions);
+            }
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/src/Internal/HostingApplication.cs b/src/Hosting/Hosting/src/Internal/HostingApplication.cs
new file mode 100644
index 0000000000000000000000000000000000000000..cd3e2d9fb21b0d1e41b2caab25ed741536b3c82a
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/HostingApplication.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.Diagnostics;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    public class HostingApplication : IHttpApplication<HostingApplication.Context>
+    {
+        private readonly RequestDelegate _application;
+        private readonly IHttpContextFactory _httpContextFactory;
+        private HostingApplicationDiagnostics _diagnostics;
+
+        public HostingApplication(
+            RequestDelegate application,
+            ILogger logger,
+            DiagnosticListener diagnosticSource,
+            IHttpContextFactory httpContextFactory)
+        {
+            _application = application;
+            _diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource);
+            _httpContextFactory = httpContextFactory;
+        }
+
+        // Set up the request
+        public Context CreateContext(IFeatureCollection contextFeatures)
+        {
+            var context = new Context();
+            var httpContext = _httpContextFactory.Create(contextFeatures);
+
+            _diagnostics.BeginRequest(httpContext, ref context);
+
+            context.HttpContext = httpContext;
+            return context;
+        }
+
+        // Execute the request
+        public Task ProcessRequestAsync(Context context)
+        {
+            return _application(context.HttpContext);
+        }
+
+        // Clean up the request
+        public void DisposeContext(Context context, Exception exception)
+        {
+            var httpContext = context.HttpContext;
+            _diagnostics.RequestEnd(httpContext, exception, context);
+            _httpContextFactory.Dispose(httpContext);
+            _diagnostics.ContextDisposed(context);
+        }
+
+        public struct Context
+        {
+            public HttpContext HttpContext { get; set; }
+            public IDisposable Scope { get; set; }
+            public long StartTimestamp { get; set; }
+            public bool EventLogEnabled { get; set; }
+            public Activity Activity { get; set; }
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d485b1a060ce9b91bf877fea19841271d0601525
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs
@@ -0,0 +1,283 @@
+// 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.Runtime.CompilerServices;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Primitives;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    internal class HostingApplicationDiagnostics
+    {
+        private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
+
+        private const string ActivityName = "Microsoft.AspNetCore.Hosting.HttpRequestIn";
+        private const string ActivityStartKey = "Microsoft.AspNetCore.Hosting.HttpRequestIn.Start";
+
+        private const string DeprecatedDiagnosticsBeginRequestKey = "Microsoft.AspNetCore.Hosting.BeginRequest";
+        private const string DeprecatedDiagnosticsEndRequestKey = "Microsoft.AspNetCore.Hosting.EndRequest";
+        private const string DiagnosticsUnhandledExceptionKey = "Microsoft.AspNetCore.Hosting.UnhandledException";
+
+        private const string RequestIdHeaderName = "Request-Id";
+        private const string CorrelationContextHeaderName = "Correlation-Context";
+
+        private readonly DiagnosticListener _diagnosticListener;
+        private readonly ILogger _logger;
+
+        public HostingApplicationDiagnostics(ILogger logger, DiagnosticListener diagnosticListener)
+        {
+            _logger = logger;
+            _diagnosticListener = diagnosticListener;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void BeginRequest(HttpContext httpContext, ref HostingApplication.Context context)
+        {
+            long startTimestamp = 0;
+
+            if (HostingEventSource.Log.IsEnabled())
+            {
+                context.EventLogEnabled = true;
+                // To keep the hot path short we defer logging in this function to non-inlines
+                RecordRequestStartEventLog(httpContext);
+            }
+
+            var diagnosticListenerEnabled = _diagnosticListener.IsEnabled();
+            var loggingEnabled = _logger.IsEnabled(LogLevel.Critical);
+
+            // If logging is enabled or the diagnostic listener is enabled, try to get the correlation
+            // id from the header
+            StringValues correlationId;
+            if (diagnosticListenerEnabled || loggingEnabled)
+            {
+                httpContext.Request.Headers.TryGetValue(RequestIdHeaderName, out correlationId);
+            }
+
+            if (diagnosticListenerEnabled)
+            {
+                if (_diagnosticListener.IsEnabled(ActivityName, httpContext))
+                {
+                    context.Activity = StartActivity(httpContext, correlationId);
+                }
+                if (_diagnosticListener.IsEnabled(DeprecatedDiagnosticsBeginRequestKey))
+                {
+                    startTimestamp = Stopwatch.GetTimestamp();
+                    RecordBeginRequestDiagnostics(httpContext, startTimestamp);
+                }
+            }
+
+            // To avoid allocation, return a null scope if the logger is not on at least to some degree.
+            if (loggingEnabled)
+            {
+                // Scope may be relevant for a different level of logging, so we always create it
+                // see: https://github.com/aspnet/Hosting/pull/944
+                // Scope can be null if logging is not on.
+                context.Scope = _logger.RequestScope(httpContext, correlationId);
+
+                if (_logger.IsEnabled(LogLevel.Information))
+                {
+                    if (startTimestamp == 0)
+                    {
+                        startTimestamp = Stopwatch.GetTimestamp();
+                    }
+
+                    // Non-inline
+                    LogRequestStarting(httpContext);
+                }
+            }
+
+            context.StartTimestamp = startTimestamp;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void RequestEnd(HttpContext httpContext, Exception exception, HostingApplication.Context context)
+        {
+            // Local cache items resolved multiple items, in order of use so they are primed in cpu pipeline when used
+            var startTimestamp = context.StartTimestamp;
+            long currentTimestamp = 0;
+
+            // If startTimestamp was 0, then Information logging wasn't enabled at for this request (and calcuated time will be wildly wrong)
+            // Is used as proxy to reduce calls to virtual: _logger.IsEnabled(LogLevel.Information)
+            if (startTimestamp != 0)
+            {
+                currentTimestamp = Stopwatch.GetTimestamp();
+                // Non-inline
+                LogRequestFinished(httpContext, startTimestamp, currentTimestamp);
+            }
+
+            if (_diagnosticListener.IsEnabled())
+            {
+                if (currentTimestamp == 0)
+                {
+                    currentTimestamp = Stopwatch.GetTimestamp();
+                }
+
+                if (exception == null)
+                {
+                    // No exception was thrown, request was sucessful
+                    if (_diagnosticListener.IsEnabled(DeprecatedDiagnosticsEndRequestKey))
+                    {
+                        // Diagnostics is enabled for EndRequest, but it may not be for BeginRequest
+                        // so call GetTimestamp if currentTimestamp is zero (from above)
+                        RecordEndRequestDiagnostics(httpContext, currentTimestamp);
+                    }
+                }
+                else
+                {
+                    // Exception was thrown from request
+                    if (_diagnosticListener.IsEnabled(DiagnosticsUnhandledExceptionKey))
+                    {
+                        // Diagnostics is enabled for UnhandledException, but it may not be for BeginRequest
+                        // so call GetTimestamp if currentTimestamp is zero (from above)
+                        RecordUnhandledExceptionDiagnostics(httpContext, currentTimestamp, exception);
+                    }
+
+                }
+
+                var activity = context.Activity;
+                // Always stop activity if it was started
+                if (activity != null)
+                {
+                    StopActivity(httpContext, activity);
+                }
+            }
+
+            if (context.EventLogEnabled && exception != null)
+            {
+                // Non-inline
+                HostingEventSource.Log.UnhandledException();
+            }
+
+            // Logging Scope is finshed with
+            context.Scope?.Dispose();
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void ContextDisposed(HostingApplication.Context context)
+        {
+            if (context.EventLogEnabled)
+            {
+                // Non-inline
+                HostingEventSource.Log.RequestStop();
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private void LogRequestStarting(HttpContext httpContext)
+        {
+            // IsEnabled is checked in the caller, so if we are here just log
+            _logger.Log(
+                logLevel: LogLevel.Information,
+                eventId: LoggerEventIds.RequestStarting,
+                state: new HostingRequestStartingLog(httpContext),
+                exception: null,
+                formatter: HostingRequestStartingLog.Callback);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private void LogRequestFinished(HttpContext httpContext, long startTimestamp, long currentTimestamp)
+        {
+            // IsEnabled isn't checked in the caller, startTimestamp > 0 is used as a fast proxy check
+            // but that may be because diagnostics are enabled, which also uses startTimestamp, so check here
+            if (_logger.IsEnabled(LogLevel.Information))
+            {
+                var elapsed = new TimeSpan((long)(TimestampToTicks * (currentTimestamp - startTimestamp)));
+
+                _logger.Log(
+                    logLevel: LogLevel.Information,
+                    eventId: LoggerEventIds.RequestFinished,
+                    state: new HostingRequestFinishedLog(httpContext, elapsed),
+                    exception: null,
+                    formatter: HostingRequestFinishedLog.Callback);
+            }
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private void RecordBeginRequestDiagnostics(HttpContext httpContext, long startTimestamp)
+        {
+            _diagnosticListener.Write(
+                DeprecatedDiagnosticsBeginRequestKey,
+                new
+                {
+                    httpContext = httpContext,
+                    timestamp = startTimestamp
+                });
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private void RecordEndRequestDiagnostics(HttpContext httpContext, long currentTimestamp)
+        {
+            _diagnosticListener.Write(
+                DeprecatedDiagnosticsEndRequestKey,
+                new
+                {
+                    httpContext = httpContext,
+                    timestamp = currentTimestamp
+                });
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private void RecordUnhandledExceptionDiagnostics(HttpContext httpContext, long currentTimestamp, Exception exception)
+        {
+            _diagnosticListener.Write(
+                DiagnosticsUnhandledExceptionKey,
+                new
+                {
+                    httpContext = httpContext,
+                    timestamp = currentTimestamp,
+                    exception = exception
+                });
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static void RecordRequestStartEventLog(HttpContext httpContext)
+        {
+            HostingEventSource.Log.RequestStart(httpContext.Request.Method, httpContext.Request.Path);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private Activity StartActivity(HttpContext httpContext, StringValues requestId)
+        {
+            var activity = new Activity(ActivityName);
+            if (!StringValues.IsNullOrEmpty(requestId))
+            {
+                activity.SetParentId(requestId);
+
+                // We expect baggage to be empty by default
+                // Only very advanced users will be using it in near future, we encourage them to keep baggage small (few items)
+                string[] baggage = httpContext.Request.Headers.GetCommaSeparatedValues(CorrelationContextHeaderName);
+                if (baggage != StringValues.Empty)
+                {
+                    foreach (var item in baggage)
+                    {
+                        if (NameValueHeaderValue.TryParse(item, out var baggageItem))
+                        {
+                            activity.AddBaggage(baggageItem.Name.ToString(), baggageItem.Value.ToString());
+                        }
+                    }
+                }
+            }
+
+            if (_diagnosticListener.IsEnabled(ActivityStartKey))
+            {
+                _diagnosticListener.StartActivity(activity, new { HttpContext = httpContext });
+            }
+            else
+            {
+                activity.Start();
+            }
+
+            return activity;
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private void StopActivity(HttpContext httpContext, Activity activity)
+        {
+            _diagnosticListener.StopActivity(activity, new { HttpContext = httpContext });
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/Internal/HostingEnvironment.cs b/src/Hosting/Hosting/src/Internal/HostingEnvironment.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1f8d1887d7d41c371beb50205d0abd9b389ac33f
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/HostingEnvironment.cs
@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.Extensions.FileProviders;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    public class HostingEnvironment : IHostingEnvironment, Extensions.Hosting.IHostingEnvironment
+    {
+        public string EnvironmentName { get; set; } = Hosting.EnvironmentName.Production;
+
+        public string ApplicationName { get; set; }
+
+        public string WebRootPath { get; set; }
+
+        public IFileProvider WebRootFileProvider { get; set; }
+
+        public string ContentRootPath { get; set; }
+
+        public IFileProvider ContentRootFileProvider { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/Internal/HostingEnvironmentExtensions.cs b/src/Hosting/Hosting/src/Internal/HostingEnvironmentExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c12d0bcdd6285610809cf7210c116b6a7981ad55
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/HostingEnvironmentExtensions.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 Microsoft.Extensions.FileProviders;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    public static class HostingEnvironmentExtensions
+    {
+        public static void Initialize(this IHostingEnvironment hostingEnvironment, string contentRootPath, WebHostOptions options)
+        {
+            if (options == null)
+            {
+                throw new ArgumentNullException(nameof(options));
+            }
+            if (string.IsNullOrEmpty(contentRootPath))
+            {
+                throw new ArgumentException("A valid non-empty content root must be provided.", nameof(contentRootPath));
+            }
+            if (!Directory.Exists(contentRootPath))
+            {
+                throw new ArgumentException($"The content root '{contentRootPath}' does not exist.", nameof(contentRootPath));
+            }
+
+            hostingEnvironment.ApplicationName = options.ApplicationName;
+            hostingEnvironment.ContentRootPath = contentRootPath;
+            hostingEnvironment.ContentRootFileProvider = new PhysicalFileProvider(hostingEnvironment.ContentRootPath);
+
+            var webRoot = options.WebRoot;
+            if (webRoot == null)
+            {
+                // Default to /wwwroot if it exists.
+                var wwwroot = Path.Combine(hostingEnvironment.ContentRootPath, "wwwroot");
+                if (Directory.Exists(wwwroot))
+                {
+                    hostingEnvironment.WebRootPath = wwwroot;
+                }
+            }
+            else
+            {
+                hostingEnvironment.WebRootPath = Path.Combine(hostingEnvironment.ContentRootPath, webRoot);
+            }
+
+            if (!string.IsNullOrEmpty(hostingEnvironment.WebRootPath))
+            {
+                hostingEnvironment.WebRootPath = Path.GetFullPath(hostingEnvironment.WebRootPath);
+                if (!Directory.Exists(hostingEnvironment.WebRootPath))
+                {
+                    Directory.CreateDirectory(hostingEnvironment.WebRootPath);
+                }
+                hostingEnvironment.WebRootFileProvider = new PhysicalFileProvider(hostingEnvironment.WebRootPath);
+            }
+            else
+            {
+                hostingEnvironment.WebRootFileProvider = new NullFileProvider();
+            }
+
+            hostingEnvironment.EnvironmentName =
+                options.Environment ??
+                hostingEnvironment.EnvironmentName;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/Internal/HostingEventSource.cs b/src/Hosting/Hosting/src/Internal/HostingEventSource.cs
new file mode 100644
index 0000000000000000000000000000000000000000..199f8aae85c74a1a82f5c2e4abe00e853715e661
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/HostingEventSource.cs
@@ -0,0 +1,55 @@
+// 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.Tracing;
+using System.Runtime.CompilerServices;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    [EventSource(Name = "Microsoft-AspNetCore-Hosting")]
+    public sealed class HostingEventSource : EventSource
+    {
+        public static readonly HostingEventSource Log = new HostingEventSource();
+
+        private HostingEventSource() { }
+
+        // NOTE
+        // - The 'Start' and 'Stop' suffixes on the following event names have special meaning in EventSource. They
+        //   enable creating 'activities'.
+        //   For more information, take a look at the following blog post:
+        //   https://blogs.msdn.microsoft.com/vancem/2015/09/14/exploring-eventsource-activity-correlation-and-causation-features/
+        // - A stop event's event id must be next one after its start event.
+
+        [Event(1, Level = EventLevel.Informational)]
+        public void HostStart()
+        {
+            WriteEvent(1);
+        }
+
+        [Event(2, Level = EventLevel.Informational)]
+        public void HostStop()
+        {
+            WriteEvent(2);
+        }
+
+        [Event(3, Level = EventLevel.Informational)]
+        public void RequestStart(string method, string path)
+        {
+            WriteEvent(3, method, path);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        [Event(4, Level = EventLevel.Informational)]
+        public void RequestStop()
+        {
+            WriteEvent(4);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        [Event(5, Level = EventLevel.Error)]
+        public void UnhandledException()
+        {
+            WriteEvent(5);
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs b/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a0579880a0a8d24de429498c25062e1b9c975a82
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs
@@ -0,0 +1,166 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Reflection;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    internal static class HostingLoggerExtensions
+    {
+        public static IDisposable RequestScope(this ILogger logger, HttpContext httpContext, string correlationId)
+        {
+            return logger.BeginScope(new HostingLogScope(httpContext, correlationId));
+        }
+
+        public static void ApplicationError(this ILogger logger, Exception exception)
+        {
+            logger.ApplicationError(
+                eventId: LoggerEventIds.ApplicationStartupException,
+                message: "Application startup exception",
+                exception: exception);
+        }
+
+        public static void HostingStartupAssemblyError(this ILogger logger, Exception exception)
+        {
+            logger.ApplicationError(
+                eventId: LoggerEventIds.HostingStartupAssemblyException,
+                message: "Hosting startup assembly exception",
+                exception: exception);
+        }
+
+        public static void ApplicationError(this ILogger logger, EventId eventId, string message, Exception exception)
+        {
+            var reflectionTypeLoadException = exception as ReflectionTypeLoadException;
+            if (reflectionTypeLoadException != null)
+            {
+                foreach (var ex in reflectionTypeLoadException.LoaderExceptions)
+                {
+                    message = message + Environment.NewLine + ex.Message;
+                }
+            }
+
+            logger.LogCritical(
+                eventId: eventId,
+                message: message,
+                exception: exception);
+        }
+
+        public static void Starting(this ILogger logger)
+        {
+            if (logger.IsEnabled(LogLevel.Debug))
+            {
+                logger.LogDebug(
+                   eventId: LoggerEventIds.Starting,
+                   message: "Hosting starting");
+            }
+        }
+
+        public static void Started(this ILogger logger)
+        {
+            if (logger.IsEnabled(LogLevel.Debug))
+            {
+                logger.LogDebug(
+                    eventId: LoggerEventIds.Started,
+                    message: "Hosting started");
+            }
+        }
+
+        public static void Shutdown(this ILogger logger)
+        {
+            if (logger.IsEnabled(LogLevel.Debug))
+            {
+                logger.LogDebug(
+                    eventId: LoggerEventIds.Shutdown,
+                    message: "Hosting shutdown");
+            }
+        }
+
+        public static void ServerShutdownException(this ILogger logger, Exception ex)
+        {
+            if (logger.IsEnabled(LogLevel.Debug))
+            {
+                logger.LogDebug(
+                    eventId: LoggerEventIds.ServerShutdownException,
+                    exception: ex,
+                    message: "Server shutdown exception");
+            }
+        }
+
+        private class HostingLogScope : IReadOnlyList<KeyValuePair<string, object>>
+        {
+            private readonly HttpContext _httpContext;
+            private readonly string _correlationId;
+
+            private string _cachedToString;
+
+            public int Count
+            {
+                get
+                {
+                    return 3;
+                }
+            }
+
+            public KeyValuePair<string, object> this[int index]
+            {
+                get
+                {
+                    if (index == 0)
+                    {
+                        return new KeyValuePair<string, object>("RequestId", _httpContext.TraceIdentifier);
+                    }
+                    else if (index == 1)
+                    {
+                        return new KeyValuePair<string, object>("RequestPath", _httpContext.Request.Path.ToString());
+                    }
+                    else if (index == 2)
+                    {
+                        return new KeyValuePair<string, object>("CorrelationId", _correlationId);
+                    }
+
+                    throw new ArgumentOutOfRangeException(nameof(index));
+                }
+            }
+
+            public HostingLogScope(HttpContext httpContext, string correlationId)
+            {
+                _httpContext = httpContext;
+                _correlationId = correlationId;
+            }
+
+            public override string ToString()
+            {
+                if (_cachedToString == null)
+                {
+                    _cachedToString = string.Format(
+                        CultureInfo.InvariantCulture,
+                        "RequestId:{0} RequestPath:{1}",
+                        _httpContext.TraceIdentifier,
+                        _httpContext.Request.Path);
+                }
+
+                return _cachedToString;
+            }
+
+            public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
+            {
+                for (int i = 0; i < Count; ++i)
+                {
+                    yield return this[i];
+                }
+            }
+
+            IEnumerator IEnumerable.GetEnumerator()
+            {
+                return GetEnumerator();
+            }
+        }
+    }
+}
+
diff --git a/src/Hosting/Hosting/src/Internal/HostingRequestFinishedLog.cs b/src/Hosting/Hosting/src/Internal/HostingRequestFinishedLog.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ab440481bae7d4f7d0800cdb4cf1547c8a097f50
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/HostingRequestFinishedLog.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;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    internal class HostingRequestFinishedLog : IReadOnlyList<KeyValuePair<string, object>>
+    {
+        internal static readonly Func<object, Exception, string> Callback = (state, exception) => ((HostingRequestFinishedLog)state).ToString();
+
+        private readonly HttpContext _httpContext;
+        private readonly TimeSpan _elapsed;
+
+        private string _cachedToString;
+
+        public int Count => 3;
+
+        public KeyValuePair<string, object> this[int index]
+        {
+            get
+            {
+                switch (index)
+                {
+                    case 0:
+                        return new KeyValuePair<string, object>("ElapsedMilliseconds", _elapsed.TotalMilliseconds);
+                    case 1:
+                        return new KeyValuePair<string, object>("StatusCode", _httpContext.Response.StatusCode);
+                    case 2:
+                        return new KeyValuePair<string, object>("ContentType", _httpContext.Response.ContentType);
+                    default:
+                        throw new IndexOutOfRangeException(nameof(index));
+                }
+            }
+        }
+
+        public HostingRequestFinishedLog(HttpContext httpContext, TimeSpan elapsed)
+        {
+            _httpContext = httpContext;
+            _elapsed = elapsed;
+        }
+
+        public override string ToString()
+        {
+            if (_cachedToString == null)
+            {
+                _cachedToString = string.Format(
+                    CultureInfo.InvariantCulture,
+                    "Request finished in {0}ms {1} {2}",
+                    _elapsed.TotalMilliseconds,
+                    _httpContext.Response.StatusCode,
+                    _httpContext.Response.ContentType);
+            }
+
+            return _cachedToString;
+        }
+
+        public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
+        {
+            for (var i = 0; i < Count; i++)
+            {
+                yield return this[i];
+            }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/src/Internal/HostingRequestStartingLog.cs b/src/Hosting/Hosting/src/Internal/HostingRequestStartingLog.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7506028a3c67cc19b5246d462142186c9ec2fce7
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/HostingRequestStartingLog.cs
@@ -0,0 +1,91 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    internal class HostingRequestStartingLog : IReadOnlyList<KeyValuePair<string, object>>
+    {
+        internal static readonly Func<object, Exception, string> Callback = (state, exception) => ((HostingRequestStartingLog)state).ToString();
+
+        private readonly HttpRequest _request;
+
+        private string _cachedToString;
+
+        public int Count => 9;
+
+        public KeyValuePair<string, object> this[int index]
+        {
+            get
+            {
+                switch (index)
+                {
+                    case 0:
+                        return new KeyValuePair<string, object>("Protocol", _request.Protocol);
+                    case 1:
+                        return new KeyValuePair<string, object>("Method", _request.Method);
+                    case 2:
+                        return new KeyValuePair<string, object>("ContentType", _request.ContentType);
+                    case 3:
+                        return new KeyValuePair<string, object>("ContentLength", _request.ContentLength);
+                    case 4:
+                        return new KeyValuePair<string, object>("Scheme", _request.Scheme.ToString());
+                    case 5:
+                        return new KeyValuePair<string, object>("Host", _request.Host.ToString());
+                    case 6:
+                        return new KeyValuePair<string, object>("PathBase", _request.PathBase.ToString());
+                    case 7:
+                        return new KeyValuePair<string, object>("Path", _request.Path.ToString());
+                    case 8:
+                        return new KeyValuePair<string, object>("QueryString", _request.QueryString.ToString());
+                    default:
+                        throw new IndexOutOfRangeException(nameof(index));
+                }
+            }
+        }
+
+        public HostingRequestStartingLog(HttpContext httpContext)
+        {
+            _request = httpContext.Request;
+        }
+
+        public override string ToString()
+        {
+            if (_cachedToString == null)
+            {
+                _cachedToString = string.Format(
+                    CultureInfo.InvariantCulture,
+                    "Request starting {0} {1} {2}://{3}{4}{5}{6} {7} {8}",
+                    _request.Protocol,
+                    _request.Method,
+                    _request.Scheme,
+                    _request.Host.Value,
+                    _request.PathBase.Value,
+                    _request.Path.Value,
+                    _request.QueryString.Value,
+                    _request.ContentType,
+                    _request.ContentLength);
+            }
+
+            return _cachedToString;
+        }
+
+        public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
+        {
+            for (var i = 0; i < Count; i++)
+            {
+                yield return this[i];
+            }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/src/Internal/LoggerEventIds.cs b/src/Hosting/Hosting/src/Internal/LoggerEventIds.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f7d8f61933a8fca96c078dd4403af35033d3bbbd
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/LoggerEventIds.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.
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    internal static class LoggerEventIds
+    {
+        public const int RequestStarting = 1;
+        public const int RequestFinished = 2;
+        public const int Starting = 3;
+        public const int Started = 4;
+        public const int Shutdown = 5;
+        public const int ApplicationStartupException = 6;
+        public const int ApplicationStoppingException = 7;
+        public const int ApplicationStoppedException = 8;
+        public const int HostedServiceStartException = 9;
+        public const int HostedServiceStopException = 10;
+        public const int HostingStartupAssemblyException = 11;
+        public const int ServerShutdownException = 12;
+    }
+}
diff --git a/src/Hosting/Hosting/src/Internal/RequestServicesContainerMiddleware.cs b/src/Hosting/Hosting/src/Internal/RequestServicesContainerMiddleware.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9fcb01aa12f631d8118feaaa1e21d812eb5a369c
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/RequestServicesContainerMiddleware.cs
@@ -0,0 +1,50 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    public class RequestServicesContainerMiddleware
+    {
+        private readonly RequestDelegate _next;
+        private readonly IServiceScopeFactory _scopeFactory;
+
+        public RequestServicesContainerMiddleware(RequestDelegate next, IServiceScopeFactory scopeFactory)
+        {
+            if (next == null)
+            {
+                throw new ArgumentNullException(nameof(next));
+            }
+            if (scopeFactory == null)
+            {
+                throw new ArgumentNullException(nameof(scopeFactory));
+            }
+
+            _next = next;
+            _scopeFactory = scopeFactory;
+        }
+
+        public Task Invoke(HttpContext httpContext)
+        {
+            Debug.Assert(httpContext != null);
+
+            var features = httpContext.Features;
+            var servicesFeature = features.Get<IServiceProvidersFeature>();
+
+            // All done if RequestServices is set
+            if (servicesFeature?.RequestServices != null)
+            {
+                return _next.Invoke(httpContext);
+            }
+
+            features.Set<IServiceProvidersFeature>(new RequestServicesFeature(httpContext, _scopeFactory));
+            return _next.Invoke(httpContext);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/Internal/RequestServicesFeature.cs b/src/Hosting/Hosting/src/Internal/RequestServicesFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a57df9bcbc493140ed35a142738f9c0a1f618cf6
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/RequestServicesFeature.cs
@@ -0,0 +1,55 @@
+// 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 Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    public class RequestServicesFeature : IServiceProvidersFeature, IDisposable
+    {
+        private readonly IServiceScopeFactory _scopeFactory;
+        private IServiceProvider _requestServices;
+        private IServiceScope _scope;
+        private bool _requestServicesSet;
+        private HttpContext _context;
+
+        public RequestServicesFeature(HttpContext context, IServiceScopeFactory scopeFactory)
+        {
+            Debug.Assert(scopeFactory != null);
+            _context = context;
+            _scopeFactory = scopeFactory;
+        }
+
+        public IServiceProvider RequestServices
+        {
+            get
+            {
+                if (!_requestServicesSet)
+                {
+                    _context.Response.RegisterForDispose(this);
+                    _scope = _scopeFactory.CreateScope();
+                    _requestServices = _scope.ServiceProvider;
+                    _requestServicesSet = true;
+                }
+                return _requestServices;
+            }
+
+            set
+            {
+                _requestServices = value;
+                _requestServicesSet = true;
+            }
+        }
+
+        public void Dispose()
+        {
+            _scope?.Dispose();
+            _scope = null;
+            _requestServices = null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/Internal/ServiceCollectionExtensions.cs b/src/Hosting/Hosting/src/Internal/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..48b8758181e4022048acbe1410e814609ee2466b
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/ServiceCollectionExtensions.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 Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    internal static class ServiceCollectionExtensions
+    {
+        public static IServiceCollection Clone(this IServiceCollection serviceCollection)
+        {
+            IServiceCollection clone = new ServiceCollection();
+            foreach (var service in serviceCollection)
+            {
+                clone.Add(service);
+            }
+            return clone;
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/src/Internal/StartupLoader.cs b/src/Hosting/Hosting/src/Internal/StartupLoader.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d7211d39d9d66b349b397f12599f2884789a8f50
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/StartupLoader.cs
@@ -0,0 +1,336 @@
+// 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.Globalization;
+using System.Linq;
+using System.Reflection;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    public class StartupLoader
+    {
+        // Creates an <see cref="StartupMethods"/> instance with the actions to run for configuring the application services and the
+        // request pipeline of the application.
+        // When using convention based startup, the process for initializing the services is as follows:
+        // The host looks for a method with the signature <see cref="IServiceProvider"/> ConfigureServices(<see cref="IServiceCollection"/> services).
+        // If it can't find one, it looks for a method with the signature <see cref="void"/> ConfigureServices(<see cref="IServiceCollection"/> services).
+        // When the configure services method is void returning, the host builds a services configuration function that runs all the <see cref="IStartupConfigureServicesFilter"/>
+        // instances registered on the host, along with the ConfigureServices method following a decorator pattern.
+        // Additionally to the ConfigureServices method, the Startup class can define a <see cref="void"/> ConfigureContainer&lt;TContainerBuilder&gt;(TContainerBuilder builder)
+        // method that further configures services into the container. If the ConfigureContainer method is defined, the services configuration function
+        // creates a TContainerBuilder <see cref="IServiceProviderFactory{TContainerBuilder}"/> and runs all the <see cref="IStartupConfigureContainerFilter{TContainerBuilder}"/>
+        // instances registered on the host, along with the ConfigureContainer method following a decorator pattern.
+        // For example:
+        // StartupFilter1
+        //   StartupFilter2
+        //     ConfigureServices
+        //   StartupFilter2
+        // StartupFilter1
+        // ConfigureContainerFilter1
+        //   ConfigureContainerFilter2
+        //     ConfigureContainer
+        //   ConfigureContainerFilter2
+        // ConfigureContainerFilter1
+        // 
+        // If the Startup class ConfigureServices returns an <see cref="IServiceProvider"/> and there is at least an <see cref="IStartupConfigureServicesFilter"/> registered we
+        // throw as the filters can't be applied.
+        public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName)
+        {
+            var configureMethod = FindConfigureDelegate(startupType, environmentName);
+
+            var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName);
+            var configureContainerMethod = FindConfigureContainerDelegate(startupType, environmentName);
+
+            object instance = null;
+            if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic))
+            {
+                instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType);
+            }
+
+            // The type of the TContainerBuilder. If there is no ConfigureContainer method we can just use object as it's not
+            // going to be used for anything.
+            var type = configureContainerMethod.MethodInfo != null ? configureContainerMethod.GetContainerType() : typeof(object);
+
+            var builder = (ConfigureServicesDelegateBuilder) Activator.CreateInstance(
+                typeof(ConfigureServicesDelegateBuilder<>).MakeGenericType(type),
+                hostingServiceProvider,
+                servicesMethod,
+                configureContainerMethod,
+                instance);
+
+            return new StartupMethods(instance, configureMethod.Build(instance), builder.Build());
+        }
+
+        private abstract class ConfigureServicesDelegateBuilder
+        {
+            public abstract Func<IServiceCollection, IServiceProvider> Build();
+        }
+
+        private class ConfigureServicesDelegateBuilder<TContainerBuilder> : ConfigureServicesDelegateBuilder
+        {
+            public ConfigureServicesDelegateBuilder(
+                IServiceProvider hostingServiceProvider,
+                ConfigureServicesBuilder configureServicesBuilder,
+                ConfigureContainerBuilder configureContainerBuilder,
+                object instance)
+            {
+                HostingServiceProvider = hostingServiceProvider;
+                ConfigureServicesBuilder = configureServicesBuilder;
+                ConfigureContainerBuilder = configureContainerBuilder;
+                Instance = instance;
+            }
+
+            public IServiceProvider HostingServiceProvider { get; }
+            public ConfigureServicesBuilder ConfigureServicesBuilder { get; }
+            public ConfigureContainerBuilder ConfigureContainerBuilder { get; }
+            public object Instance { get; }
+
+            public override Func<IServiceCollection, IServiceProvider> Build()
+            {
+                ConfigureServicesBuilder.StartupServiceFilters = BuildStartupServicesFilterPipeline;
+                var configureServicesCallback = ConfigureServicesBuilder.Build(Instance);
+
+                ConfigureContainerBuilder.ConfigureContainerFilters = ConfigureContainerPipeline;
+                var configureContainerCallback = ConfigureContainerBuilder.Build(Instance);
+
+                return ConfigureServices(configureServicesCallback, configureContainerCallback);
+
+                Action<object> ConfigureContainerPipeline(Action<object> action)
+                {
+                    return Target;
+
+                    // The ConfigureContainer pipeline needs an Action<TContainerBuilder> as source, so we just adapt the
+                    // signature with this function.
+                    void Source(TContainerBuilder containerBuilder) => 
+                        action(containerBuilder);
+
+                    // The ConfigureContainerBuilder.ConfigureContainerFilters expects an Action<object> as value, but our pipeline
+                    // produces an Action<TContainerBuilder> given a source, so we wrap it on an Action<object> that internally casts
+                    // the object containerBuilder to TContainerBuilder to match the expected signature of our ConfigureContainer pipeline.
+                    void Target(object containerBuilder) => 
+                        BuildStartupConfigureContainerFiltersPipeline(Source)((TContainerBuilder)containerBuilder);
+                }
+            }
+
+            Func<IServiceCollection, IServiceProvider> ConfigureServices(
+                Func<IServiceCollection, IServiceProvider> configureServicesCallback,
+                Action<object> configureContainerCallback)
+            {
+                return ConfigureServicesWithContainerConfiguration;
+
+                IServiceProvider ConfigureServicesWithContainerConfiguration(IServiceCollection services)
+                {
+                    // Call ConfigureServices, if that returned an IServiceProvider, we're done
+                    IServiceProvider applicationServiceProvider = configureServicesCallback.Invoke(services);
+
+                    if (applicationServiceProvider != null)
+                    {
+                        return applicationServiceProvider;
+                    }
+
+                    // If there's a ConfigureContainer method
+                    if (ConfigureContainerBuilder.MethodInfo != null)
+                    {
+                        var serviceProviderFactory = HostingServiceProvider.GetRequiredService<IServiceProviderFactory<TContainerBuilder>>();
+                        var builder = serviceProviderFactory.CreateBuilder(services);
+                        configureContainerCallback(builder);
+                        applicationServiceProvider = serviceProviderFactory.CreateServiceProvider(builder);
+                    }
+                    else
+                    {
+                        // Get the default factory
+                        var serviceProviderFactory = HostingServiceProvider.GetRequiredService<IServiceProviderFactory<IServiceCollection>>();
+                        var builder = serviceProviderFactory.CreateBuilder(services);
+                        applicationServiceProvider = serviceProviderFactory.CreateServiceProvider(builder);
+                    }
+
+                    return applicationServiceProvider ?? services.BuildServiceProvider();
+                }
+            }
+
+            private Func<IServiceCollection, IServiceProvider> BuildStartupServicesFilterPipeline(Func<IServiceCollection, IServiceProvider> startup)
+            {
+                return RunPipeline;
+
+                IServiceProvider RunPipeline(IServiceCollection services)
+                {
+                    var filters = HostingServiceProvider.GetRequiredService<IEnumerable<IStartupConfigureServicesFilter>>().Reverse().ToArray();
+
+                    // If there are no filters just run startup (makes IServiceProvider ConfigureServices(IServiceCollection services) work.
+                    if (filters.Length == 0)
+                    {
+                        return startup(services);
+                    }
+
+                    Action<IServiceCollection> pipeline = InvokeStartup;
+                    for (int i = 0; i < filters.Length; i++)
+                    {
+                        pipeline = filters[i].ConfigureServices(pipeline);
+                    }
+
+                    pipeline(services);
+
+                    // We return null so that the host here builds the container (same result as void ConfigureServices(IServiceCollection services);
+                    return null;
+
+                    void InvokeStartup(IServiceCollection serviceCollection)
+                    {
+                        var result = startup(serviceCollection);
+                        if (filters.Length > 0 && result != null)
+                        {
+                            // public IServiceProvider ConfigureServices(IServiceCollection serviceCollection) is not compatible with IStartupServicesFilter;
+                            var message = $"A ConfigureServices method that returns an {nameof(IServiceProvider)} is " +
+                                $"not compatible with the use of one or more {nameof(IStartupConfigureServicesFilter)}. " +
+                                $"Use a void returning ConfigureServices method instead or a ConfigureContainer method.";
+                            throw new InvalidOperationException(message);
+                        };
+                    }
+                }
+            }
+
+            private Action<TContainerBuilder> BuildStartupConfigureContainerFiltersPipeline(Action<TContainerBuilder> configureContainer)
+            {
+                return RunPipeline;
+
+                void RunPipeline(TContainerBuilder containerBuilder)
+                {
+                    var filters = HostingServiceProvider
+                        .GetRequiredService<IEnumerable<IStartupConfigureContainerFilter<TContainerBuilder>>>()
+                        .Reverse()
+                        .ToArray();
+
+                    Action<TContainerBuilder> pipeline = InvokeConfigureContainer;
+                    for (int i = 0; i < filters.Length; i++)
+                    {
+                        pipeline = filters[i].ConfigureContainer(pipeline);
+                    }
+
+                    pipeline(containerBuilder);
+
+                    void InvokeConfigureContainer(TContainerBuilder builder) => configureContainer(builder);
+                }
+            }
+        }
+
+        public static Type FindStartupType(string startupAssemblyName, string environmentName)
+        {
+            if (string.IsNullOrEmpty(startupAssemblyName))
+            {
+                throw new ArgumentException(
+                    string.Format("A startup method, startup type or startup assembly is required. If specifying an assembly, '{0}' cannot be null or empty.",
+                    nameof(startupAssemblyName)),
+                    nameof(startupAssemblyName));
+            }
+
+            var assembly = Assembly.Load(new AssemblyName(startupAssemblyName));
+            if (assembly == null)
+            {
+                throw new InvalidOperationException(String.Format("The assembly '{0}' failed to load.", startupAssemblyName));
+            }
+
+            var startupNameWithEnv = "Startup" + environmentName;
+            var startupNameWithoutEnv = "Startup";
+
+            // Check the most likely places first
+            var type =
+                assembly.GetType(startupNameWithEnv) ??
+                assembly.GetType(startupAssemblyName + "." + startupNameWithEnv) ??
+                assembly.GetType(startupNameWithoutEnv) ??
+                assembly.GetType(startupAssemblyName + "." + startupNameWithoutEnv);
+
+            if (type == null)
+            {
+                // Full scan
+                var definedTypes = assembly.DefinedTypes.ToList();
+
+                var startupType1 = definedTypes.Where(info => info.Name.Equals(startupNameWithEnv, StringComparison.OrdinalIgnoreCase));
+                var startupType2 = definedTypes.Where(info => info.Name.Equals(startupNameWithoutEnv, StringComparison.OrdinalIgnoreCase));
+
+                var typeInfo = startupType1.Concat(startupType2).FirstOrDefault();
+                if (typeInfo != null)
+                {
+                    type = typeInfo.AsType();
+                }
+            }
+
+            if (type == null)
+            {
+                throw new InvalidOperationException(String.Format("A type named '{0}' or '{1}' could not be found in assembly '{2}'.",
+                    startupNameWithEnv,
+                    startupNameWithoutEnv,
+                    startupAssemblyName));
+            }
+
+            return type;
+        }
+
+        private static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName)
+        {
+            var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true);
+            return new ConfigureBuilder(configureMethod);
+        }
+
+        private static ConfigureContainerBuilder FindConfigureContainerDelegate(Type startupType, string environmentName)
+        {
+            var configureMethod = FindMethod(startupType, "Configure{0}Container", environmentName, typeof(void), required: false);
+            return new ConfigureContainerBuilder(configureMethod);
+        }
+
+        private static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName)
+        {
+            var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false)
+                ?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false);
+            return new ConfigureServicesBuilder(servicesMethod);
+        }
+
+        private static MethodInfo FindMethod(Type startupType, string methodName, string environmentName, Type returnType = null, bool required = true)
+        {
+            var methodNameWithEnv = string.Format(CultureInfo.InvariantCulture, methodName, environmentName);
+            var methodNameWithNoEnv = string.Format(CultureInfo.InvariantCulture, methodName, "");
+
+            var methods = startupType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
+            var selectedMethods = methods.Where(method => method.Name.Equals(methodNameWithEnv, StringComparison.OrdinalIgnoreCase)).ToList();
+            if (selectedMethods.Count > 1)
+            {
+                throw new InvalidOperationException(string.Format("Having multiple overloads of method '{0}' is not supported.", methodNameWithEnv));
+            }
+            if (selectedMethods.Count == 0)
+            {
+                selectedMethods = methods.Where(method => method.Name.Equals(methodNameWithNoEnv, StringComparison.OrdinalIgnoreCase)).ToList();
+                if (selectedMethods.Count > 1)
+                {
+                    throw new InvalidOperationException(string.Format("Having multiple overloads of method '{0}' is not supported.", methodNameWithNoEnv));
+                }
+            }
+
+            var methodInfo = selectedMethods.FirstOrDefault();
+            if (methodInfo == null)
+            {
+                if (required)
+                {
+                    throw new InvalidOperationException(string.Format("A public method named '{0}' or '{1}' could not be found in the '{2}' type.",
+                        methodNameWithEnv,
+                        methodNameWithNoEnv,
+                        startupType.FullName));
+
+                }
+                return null;
+            }
+            if (returnType != null && methodInfo.ReturnType != returnType)
+            {
+                if (required)
+                {
+                    throw new InvalidOperationException(string.Format("The '{0}' method in the type '{1}' must have a return type of '{2}'.",
+                        methodInfo.Name,
+                        startupType.FullName,
+                        returnType.Name));
+                }
+                return null;
+            }
+            return methodInfo;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/Internal/StartupMethods.cs b/src/Hosting/Hosting/src/Internal/StartupMethods.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f854c859465c1580fa2424f5dcc27f11294c5544
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/StartupMethods.cs
@@ -0,0 +1,28 @@
+// 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 Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    public class StartupMethods
+    {
+        public StartupMethods(object instance, Action<IApplicationBuilder> configure, Func<IServiceCollection, IServiceProvider> configureServices)
+        {
+            Debug.Assert(configure != null);
+            Debug.Assert(configureServices != null);
+
+            StartupInstance = instance;
+            ConfigureDelegate = configure;
+            ConfigureServicesDelegate = configureServices;
+        }
+
+        public object StartupInstance { get; }
+        public Func<IServiceCollection, IServiceProvider> ConfigureServicesDelegate { get; }
+        public Action<IApplicationBuilder> ConfigureDelegate { get; }
+
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/Internal/WebHost.cs b/src/Hosting/Hosting/src/Internal/WebHost.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3764427f63eab087315531cad4317d76a6b89d50
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/WebHost.cs
@@ -0,0 +1,362 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.ExceptionServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting.Builder;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.AspNetCore.Hosting.Views;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.StackTrace.Sources;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    internal class WebHost : IWebHost
+    {
+        private static readonly string DeprecatedServerUrlsKey = "server.urls";
+
+        private readonly IServiceCollection _applicationServiceCollection;
+        private IStartup _startup;
+        private ApplicationLifetime _applicationLifetime;
+        private HostedServiceExecutor _hostedServiceExecutor;
+
+        private readonly IServiceProvider _hostingServiceProvider;
+        private readonly WebHostOptions _options;
+        private readonly IConfiguration _config;
+        private readonly AggregateException _hostingStartupErrors;
+
+        private IServiceProvider _applicationServices;
+        private ExceptionDispatchInfo _applicationServicesException;
+        private ILogger<WebHost> _logger;
+
+        private bool _stopped;
+
+        // Used for testing only
+        internal WebHostOptions Options => _options;
+
+        private IServer Server { get; set; }
+
+        public WebHost(
+            IServiceCollection appServices,
+            IServiceProvider hostingServiceProvider,
+            WebHostOptions options,
+            IConfiguration config,
+            AggregateException hostingStartupErrors)
+        {
+            if (appServices == null)
+            {
+                throw new ArgumentNullException(nameof(appServices));
+            }
+
+            if (hostingServiceProvider == null)
+            {
+                throw new ArgumentNullException(nameof(hostingServiceProvider));
+            }
+
+            if (config == null)
+            {
+                throw new ArgumentNullException(nameof(config));
+            }
+
+            _config = config;
+            _hostingStartupErrors = hostingStartupErrors;
+            _options = options;
+            _applicationServiceCollection = appServices;
+            _hostingServiceProvider = hostingServiceProvider;
+            _applicationServiceCollection.AddSingleton<IApplicationLifetime, ApplicationLifetime>();
+            // There's no way to to register multiple service types per definition. See https://github.com/aspnet/DependencyInjection/issues/360
+            _applicationServiceCollection.AddSingleton(sp =>
+            {
+                return sp.GetRequiredService<IApplicationLifetime>() as Extensions.Hosting.IApplicationLifetime;
+            });
+            _applicationServiceCollection.AddSingleton<HostedServiceExecutor>();
+        }
+
+        public IServiceProvider Services
+        {
+            get
+            {
+                return _applicationServices;
+            }
+        }
+
+        public IFeatureCollection ServerFeatures
+        {
+            get
+            {
+                EnsureServer();
+                return Server?.Features;
+            }
+        }
+
+        // Called immediately after the constructor so the properties can rely on it.
+        public void Initialize()
+        {
+            try
+            {
+                EnsureApplicationServices();
+            }
+            catch (Exception ex)
+            {
+                // EnsureApplicationServices may have failed due to a missing or throwing Startup class.
+                if (_applicationServices == null)
+                {
+                    _applicationServices = _applicationServiceCollection.BuildServiceProvider();
+                }
+
+                if (!_options.CaptureStartupErrors)
+                {
+                    throw;
+                }
+
+                _applicationServicesException = ExceptionDispatchInfo.Capture(ex);
+            }
+        }
+
+        public void Start()
+        {
+            StartAsync().GetAwaiter().GetResult();
+        }
+
+        public virtual async Task StartAsync(CancellationToken cancellationToken = default)
+        {
+            HostingEventSource.Log.HostStart();
+            _logger = _applicationServices.GetRequiredService<ILogger<WebHost>>();
+            _logger.Starting();
+
+            var application = BuildApplication();
+
+            _applicationLifetime = _applicationServices.GetRequiredService<IApplicationLifetime>() as ApplicationLifetime;
+            _hostedServiceExecutor = _applicationServices.GetRequiredService<HostedServiceExecutor>();
+            var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticListener>();
+            var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
+            var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory);
+            await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);
+
+            // Fire IApplicationLifetime.Started
+            _applicationLifetime?.NotifyStarted();
+
+            // Fire IHostedService.Start
+            await _hostedServiceExecutor.StartAsync(cancellationToken).ConfigureAwait(false);
+
+            _logger.Started();
+
+            // Log the fact that we did load hosting startup assemblies.
+            if (_logger.IsEnabled(LogLevel.Debug))
+            {
+                foreach (var assembly in _options.GetFinalHostingStartupAssemblies())
+                {
+                    _logger.LogDebug("Loaded hosting startup assembly {assemblyName}", assembly);
+                }
+            }
+
+            if (_hostingStartupErrors != null)
+            {
+                foreach (var exception in _hostingStartupErrors.InnerExceptions)
+                {
+                    _logger.HostingStartupAssemblyError(exception);
+                }
+            }
+        }
+
+        private void EnsureApplicationServices()
+        {
+            if (_applicationServices == null)
+            {
+                EnsureStartup();
+                _applicationServices = _startup.ConfigureServices(_applicationServiceCollection);
+            }
+        }
+
+        private void EnsureStartup()
+        {
+            if (_startup != null)
+            {
+                return;
+            }
+
+            _startup = _hostingServiceProvider.GetService<IStartup>();
+
+            if (_startup == null)
+            {
+                throw new InvalidOperationException($"No startup configured. Please specify startup via WebHostBuilder.UseStartup, WebHostBuilder.Configure, injecting {nameof(IStartup)} or specifying the startup assembly via {nameof(WebHostDefaults.StartupAssemblyKey)} in the web host configuration.");
+            }
+        }
+
+        private RequestDelegate BuildApplication()
+        {
+            try
+            {
+                _applicationServicesException?.Throw();
+                EnsureServer();
+
+                var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
+                var builder = builderFactory.CreateBuilder(Server.Features);
+                builder.ApplicationServices = _applicationServices;
+
+                var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
+                Action<IApplicationBuilder> configure = _startup.Configure;
+                foreach (var filter in startupFilters.Reverse())
+                {
+                    configure = filter.Configure(configure);
+                }
+
+                configure(builder);
+
+                return builder.Build();
+            }
+            catch (Exception ex)
+            {
+                if (!_options.SuppressStatusMessages)
+                {
+                    // Write errors to standard out so they can be retrieved when not in development mode.
+                    Console.WriteLine("Application startup exception: " + ex.ToString());
+                }
+                var logger = _applicationServices.GetRequiredService<ILogger<WebHost>>();
+                logger.ApplicationError(ex);
+
+                if (!_options.CaptureStartupErrors)
+                {
+                    throw;
+                }
+
+                EnsureServer();
+
+                // Generate an HTML error page.
+                var hostingEnv = _applicationServices.GetRequiredService<IHostingEnvironment>();
+                var showDetailedErrors = hostingEnv.IsDevelopment() || _options.DetailedErrors;
+
+                var model = new ErrorPageModel
+                {
+                    RuntimeDisplayName = RuntimeInformation.FrameworkDescription
+                };
+                var systemRuntimeAssembly = typeof(System.ComponentModel.DefaultValueAttribute).GetTypeInfo().Assembly;
+                var assemblyVersion = new AssemblyName(systemRuntimeAssembly.FullName).Version.ToString();
+                var clrVersion = assemblyVersion;
+                model.RuntimeArchitecture = RuntimeInformation.ProcessArchitecture.ToString();
+                var currentAssembly = typeof(ErrorPage).GetTypeInfo().Assembly;
+                model.CurrentAssemblyVesion = currentAssembly
+                    .GetCustomAttribute<AssemblyInformationalVersionAttribute>()
+                    .InformationalVersion;
+                model.ClrVersion = clrVersion;
+                model.OperatingSystemDescription = RuntimeInformation.OSDescription;
+
+                if (showDetailedErrors)
+                {
+                    var exceptionDetailProvider = new ExceptionDetailsProvider(
+                        hostingEnv.ContentRootFileProvider,
+                        sourceCodeLineCount: 6);
+
+                    model.ErrorDetails = exceptionDetailProvider.GetDetails(ex);
+                }
+                else
+                {
+                    model.ErrorDetails = new ExceptionDetails[0];
+                }
+
+                var errorPage = new ErrorPage(model);
+                return context =>
+                {
+                    context.Response.StatusCode = 500;
+                    context.Response.Headers["Cache-Control"] = "no-cache";
+                    return errorPage.ExecuteAsync(context);
+                };
+            }
+        }
+
+        private void EnsureServer()
+        {
+            if (Server == null)
+            {
+                Server = _applicationServices.GetRequiredService<IServer>();
+
+                var serverAddressesFeature = Server.Features?.Get<IServerAddressesFeature>();
+                var addresses = serverAddressesFeature?.Addresses;
+                if (addresses != null && !addresses.IsReadOnly && addresses.Count == 0)
+                {
+                    var urls = _config[WebHostDefaults.ServerUrlsKey] ?? _config[DeprecatedServerUrlsKey];
+                    if (!string.IsNullOrEmpty(urls))
+                    {
+                        serverAddressesFeature.PreferHostingUrls = WebHostUtilities.ParseBool(_config, WebHostDefaults.PreferHostingUrlsKey);
+
+                        foreach (var value in urls.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
+                        {
+                            addresses.Add(value);
+                        }
+                    }
+                }
+            }
+        }
+
+        public async Task StopAsync(CancellationToken cancellationToken = default)
+        {
+            if (_stopped)
+            {
+                return;
+            }
+            _stopped = true;
+
+            _logger?.Shutdown();
+
+            var timeoutToken = new CancellationTokenSource(Options.ShutdownTimeout).Token;
+            if (!cancellationToken.CanBeCanceled)
+            {
+                cancellationToken = timeoutToken;
+            }
+            else
+            {
+                cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutToken).Token;
+            }
+
+            // Fire IApplicationLifetime.Stopping
+            _applicationLifetime?.StopApplication();
+
+            if (Server != null)
+            {
+                await Server.StopAsync(cancellationToken).ConfigureAwait(false);
+            }
+
+            // Fire the IHostedService.Stop
+            if (_hostedServiceExecutor != null)
+            {
+                await _hostedServiceExecutor.StopAsync(cancellationToken).ConfigureAwait(false);
+            }
+
+            // Fire IApplicationLifetime.Stopped
+            _applicationLifetime?.NotifyStopped();
+
+            HostingEventSource.Log.HostStop();
+        }
+
+        public void Dispose()
+        {
+            if (!_stopped)
+            {
+                try
+                {
+                    StopAsync().GetAwaiter().GetResult();
+                }
+                catch (Exception ex)
+                {
+                    _logger?.ServerShutdownException(ex);
+                }
+            }
+
+            (_applicationServices as IDisposable)?.Dispose();
+            (_hostingServiceProvider as IDisposable)?.Dispose();
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/src/Internal/WebHostOptions.cs b/src/Hosting/Hosting/src/Internal/WebHostOptions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e9e611bc699b4ce453d947912e31488bcf8158c2
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/WebHostOptions.cs
@@ -0,0 +1,96 @@
+// 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.Globalization;
+using System.Linq;
+using Microsoft.Extensions.Configuration;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    public class WebHostOptions
+    {
+        public WebHostOptions() { }
+
+        public WebHostOptions(IConfiguration configuration)
+            : this(configuration, string.Empty) { }
+
+        public WebHostOptions(IConfiguration configuration, string applicationNameFallback)
+        {
+            if (configuration == null)
+            {
+                throw new ArgumentNullException(nameof(configuration));
+            }
+
+            ApplicationName = configuration[WebHostDefaults.ApplicationKey] ?? applicationNameFallback;
+            StartupAssembly = configuration[WebHostDefaults.StartupAssemblyKey];
+            DetailedErrors = WebHostUtilities.ParseBool(configuration, WebHostDefaults.DetailedErrorsKey);
+            CaptureStartupErrors = WebHostUtilities.ParseBool(configuration, WebHostDefaults.CaptureStartupErrorsKey);
+            Environment = configuration[WebHostDefaults.EnvironmentKey];
+            WebRoot = configuration[WebHostDefaults.WebRootKey];
+            ContentRootPath = configuration[WebHostDefaults.ContentRootKey];
+            PreventHostingStartup = WebHostUtilities.ParseBool(configuration, WebHostDefaults.PreventHostingStartupKey);
+            SuppressStatusMessages = WebHostUtilities.ParseBool(configuration, WebHostDefaults.SuppressStatusMessagesKey);
+
+            // Search the primary assembly and configured assemblies.
+            HostingStartupAssemblies = Split($"{ApplicationName};{configuration[WebHostDefaults.HostingStartupAssembliesKey]}");
+            HostingStartupExcludeAssemblies = Split(configuration[WebHostDefaults.HostingStartupExcludeAssembliesKey]);
+
+            var timeout = configuration[WebHostDefaults.ShutdownTimeoutKey];
+            if (!string.IsNullOrEmpty(timeout)
+                && int.TryParse(timeout, NumberStyles.None, CultureInfo.InvariantCulture, out var seconds))
+            {
+                ShutdownTimeout = TimeSpan.FromSeconds(seconds);
+            }
+        }
+
+        public string ApplicationName { get; set; }
+
+        public bool PreventHostingStartup { get; set; }
+
+        public bool SuppressStatusMessages { get; set; }
+
+        public IReadOnlyList<string> HostingStartupAssemblies { get; set; }
+
+        public IReadOnlyList<string> HostingStartupExcludeAssemblies { get; set; }
+
+        public bool DetailedErrors { get; set; }
+
+        public bool CaptureStartupErrors { get; set; }
+
+        public string Environment { get; set; }
+
+        public string StartupAssembly { get; set; }
+
+        public string WebRoot { get; set; }
+
+        public string ContentRootPath { get; set; }
+
+        public TimeSpan ShutdownTimeout { get; set; } = TimeSpan.FromSeconds(5);
+
+        public IEnumerable<string> GetFinalHostingStartupAssemblies()
+        {
+            return HostingStartupAssemblies.Except(HostingStartupExcludeAssemblies, StringComparer.OrdinalIgnoreCase);
+        }
+
+        private IReadOnlyList<string> Split(string value)
+        {
+            if (string.IsNullOrWhiteSpace(value))
+            {
+                return Array.Empty<string>();
+            }
+
+            var list = new List<string>();
+            foreach (var part in value.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
+            {
+                var trimmedPart = part;
+                if (!string.IsNullOrEmpty(trimmedPart))
+                {
+                    list.Add(trimmedPart);
+                }
+            }
+            return list;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/Internal/WebHostUtilities.cs b/src/Hosting/Hosting/src/Internal/WebHostUtilities.cs
new file mode 100644
index 0000000000000000000000000000000000000000..49635699d15ae39a294ade2d34b3004dd027341e
--- /dev/null
+++ b/src/Hosting/Hosting/src/Internal/WebHostUtilities.cs
@@ -0,0 +1,17 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Extensions.Configuration;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    public class WebHostUtilities
+    {
+        public static bool ParseBool(IConfiguration configuration, string key)
+        {
+            return string.Equals("true", configuration[key], StringComparison.OrdinalIgnoreCase)
+                || string.Equals("1", configuration[key], StringComparison.OrdinalIgnoreCase);
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..627b36bbc2f155818dc0ab796eb74e8a62609453
--- /dev/null
+++ b/src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj
@@ -0,0 +1,30 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>ASP.NET Core hosting infrastructure and startup logic for web applications.</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <NoWarn>$(NoWarn);CS1591</NoWarn>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore;hosting</PackageTags>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
+    <Reference Include="Microsoft.AspNetCore.Http.Extensions" />
+    <Reference Include="Microsoft.AspNetCore.Http" />
+    <Reference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
+    <Reference Include="Microsoft.Extensions.Configuration.FileExtensions" />
+    <Reference Include="Microsoft.Extensions.Configuration" />
+    <Reference Include="Microsoft.Extensions.DependencyInjection" />
+    <Reference Include="Microsoft.Extensions.FileProviders.Physical" />
+    <Reference Include="Microsoft.Extensions.Hosting.Abstractions" />
+    <Reference Include="Microsoft.Extensions.Logging" />
+    <Reference Include="Microsoft.Extensions.Options" />
+    <Reference Include="Microsoft.Extensions.RazorViews.Sources" PrivateAssets="All" />
+    <Reference Include="Microsoft.Extensions.StackTrace.Sources" PrivateAssets="All" />
+    <Reference Include="Microsoft.Extensions.TypeNameHelper.Sources" PrivateAssets="All" />
+    <Reference Include="System.Diagnostics.DiagnosticSource" />
+    <Reference Include="System.Reflection.Metadata" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Hosting/Hosting/src/Properties/AssemblyInfo.cs b/src/Hosting/Hosting/src/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..39703dc79d2aa03b7ec7552b9c21a4994299c68c
--- /dev/null
+++ b/src/Hosting/Hosting/src/Properties/AssemblyInfo.cs
@@ -0,0 +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.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Hosting.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/Hosting/Hosting/src/Properties/Resources.Designer.cs b/src/Hosting/Hosting/src/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..088072729cd2b2b684b42ee9d636befcbb58b3a9
--- /dev/null
+++ b/src/Hosting/Hosting/src/Properties/Resources.Designer.cs
@@ -0,0 +1,94 @@
+// <auto-generated />
+namespace Microsoft.AspNetCore.Hosting
+{
+    using System.Globalization;
+    using System.Reflection;
+    using System.Resources;
+
+    internal static class Resources
+    {
+        private static readonly ResourceManager _resourceManager
+            = new ResourceManager("Microsoft.AspNetCore.Hosting.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+        /// <summary>
+        /// Internal Server Error
+        /// </summary>
+        internal static string ErrorPageHtml_Title
+        {
+            get { return GetString("ErrorPageHtml_Title"); }
+        }
+
+        /// <summary>
+        /// Internal Server Error
+        /// </summary>
+        internal static string FormatErrorPageHtml_Title()
+        {
+            return GetString("ErrorPageHtml_Title");
+        }
+
+        /// <summary>
+        /// An error occurred while starting the application.
+        /// </summary>
+        internal static string ErrorPageHtml_UnhandledException
+        {
+            get { return GetString("ErrorPageHtml_UnhandledException"); }
+        }
+
+        /// <summary>
+        /// An error occurred while starting the application.
+        /// </summary>
+        internal static string FormatErrorPageHtml_UnhandledException()
+        {
+            return GetString("ErrorPageHtml_UnhandledException");
+        }
+
+        /// <summary>
+        /// Unknown location
+        /// </summary>
+        internal static string ErrorPageHtml_UnknownLocation
+        {
+            get { return GetString("ErrorPageHtml_UnknownLocation"); }
+        }
+
+        /// <summary>
+        /// Unknown location
+        /// </summary>
+        internal static string FormatErrorPageHtml_UnknownLocation()
+        {
+            return GetString("ErrorPageHtml_UnknownLocation");
+        }
+
+        /// <summary>
+        /// WebHostBuilder allows creation only of a single instance of WebHost
+        /// </summary>
+        internal static string WebHostBuilder_SingleInstance
+        {
+            get { return GetString("WebHostBuilder_SingleInstance"); }
+        }
+
+        /// <summary>
+        /// WebHostBuilder allows creation only of a single instance of WebHost
+        /// </summary>
+        internal static string FormatWebHostBuilder_SingleInstance()
+        {
+            return GetString("WebHostBuilder_SingleInstance");
+        }
+
+        private static string GetString(string name, params string[] formatterNames)
+        {
+            var value = _resourceManager.GetString(name);
+
+            System.Diagnostics.Debug.Assert(value != null);
+
+            if (formatterNames != null)
+            {
+                for (var i = 0; i < formatterNames.Length; i++)
+                {
+                    value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+                }
+            }
+
+            return value;
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/src/Resources.resx b/src/Hosting/Hosting/src/Resources.resx
new file mode 100644
index 0000000000000000000000000000000000000000..64d6fe523daeb909cb3472f31511350ec441bfa9
--- /dev/null
+++ b/src/Hosting/Hosting/src/Resources.resx
@@ -0,0 +1,132 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="ErrorPageHtml_Title" xml:space="preserve">
+    <value>Internal Server Error</value>
+  </data>
+  <data name="ErrorPageHtml_UnhandledException" xml:space="preserve">
+    <value>An error occurred while starting the application.</value>
+  </data>
+  <data name="ErrorPageHtml_UnknownLocation" xml:space="preserve">
+    <value>Unknown location</value>
+  </data>
+  <data name="WebHostBuilder_SingleInstance" xml:space="preserve">
+    <value>WebHostBuilder allows creation only of a single instance of WebHost</value>
+  </data>
+</root>
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/Server/Features/ServerAddressesFeature.cs b/src/Hosting/Hosting/src/Server/Features/ServerAddressesFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..098ec8cdb0622e58e95e867c5e1ac1bc6f5b881b
--- /dev/null
+++ b/src/Hosting/Hosting/src/Server/Features/ServerAddressesFeature.cs
@@ -0,0 +1,14 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Hosting.Server.Features
+{
+    public class ServerAddressesFeature : IServerAddressesFeature
+    {
+        public ICollection<string> Addresses { get; } = new List<string>();
+
+        public bool PreferHostingUrls { get; set; }
+    }
+}
diff --git a/src/Hosting/Hosting/src/Startup/ConventionBasedStartup.cs b/src/Hosting/Hosting/src/Startup/ConventionBasedStartup.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b31f9478d1f6e3be0015cbdd31225c10822b9599
--- /dev/null
+++ b/src/Hosting/Hosting/src/Startup/ConventionBasedStartup.cs
@@ -0,0 +1,56 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Reflection;
+using System.Runtime.ExceptionServices;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting.Internal;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    public class ConventionBasedStartup : IStartup
+    {
+        private readonly StartupMethods _methods;
+
+        public ConventionBasedStartup(StartupMethods methods)
+        {
+            _methods = methods;
+        }
+        
+        public void Configure(IApplicationBuilder app)
+        {
+            try
+            {
+                _methods.ConfigureDelegate(app);
+            }
+            catch (Exception ex)
+            {
+                if (ex is TargetInvocationException)
+                {
+                    ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
+                }
+
+                throw;
+            }
+        }
+
+        public IServiceProvider ConfigureServices(IServiceCollection services)
+        {
+            try
+            {
+                return _methods.ConfigureServicesDelegate(services);
+            }
+            catch (Exception ex)
+            {
+                if (ex is TargetInvocationException)
+                {
+                    ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
+                }
+
+                throw;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/Startup/DelegateStartup.cs b/src/Hosting/Hosting/src/Startup/DelegateStartup.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d354ad946e5d67d17bec1e86eafd48cb3ad10cc9
--- /dev/null
+++ b/src/Hosting/Hosting/src/Startup/DelegateStartup.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.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    public class DelegateStartup : StartupBase<IServiceCollection>
+    {
+        private Action<IApplicationBuilder> _configureApp;
+
+        public DelegateStartup(IServiceProviderFactory<IServiceCollection> factory, Action<IApplicationBuilder> configureApp) : base(factory)
+        {
+            _configureApp = configureApp;
+        }
+
+        public override void Configure(IApplicationBuilder app) => _configureApp(app);
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.Designer.cs b/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.Designer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..52a6db45d33d52dc56e89ad7785e16db74c02688
--- /dev/null
+++ b/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.Designer.cs
@@ -0,0 +1,1055 @@
+namespace Microsoft.AspNetCore.Hosting.Views
+{
+#line 1 "ErrorPage.cshtml"
+using System
+
+#line default
+#line hidden
+    ;
+#line 2 "ErrorPage.cshtml"
+using System.Globalization
+
+#line default
+#line hidden
+    ;
+#line 3 "ErrorPage.cshtml"
+using System.Linq
+
+#line default
+#line hidden
+    ;
+#line 4 "ErrorPage.cshtml"
+using System.Net
+
+#line default
+#line hidden
+    ;
+#line 5 "ErrorPage.cshtml"
+using System.Reflection
+
+#line default
+#line hidden
+    ;
+#line 6 "ErrorPage.cshtml"
+using Microsoft.AspNetCore.Hosting.Views
+
+#line default
+#line hidden
+    ;
+    using System.Threading.Tasks;
+
+    internal class ErrorPage : Microsoft.Extensions.RazorViews.BaseView
+    {
+#line 9 "ErrorPage.cshtml"
+
+    public ErrorPage(ErrorPageModel model)
+    {
+        Model = model;
+    }
+
+    public ErrorPageModel Model { get; set; }
+
+#line default
+#line hidden
+        #line hidden
+        public ErrorPage()
+        {
+        }
+
+        #pragma warning disable 1998
+        public override async Task ExecuteAsync()
+        {
+            WriteLiteral("\r\n");
+#line 17 "ErrorPage.cshtml"
+  
+    Response.ContentType = "text/html; charset=utf-8";
+    var location = string.Empty;
+
+#line default
+#line hidden
+
+            WriteLiteral("<!DOCTYPE html>\r\n<html");
+            BeginWriteAttribute("lang", " lang=\"", 422, "\"", 483, 1);
+#line 22 "ErrorPage.cshtml"
+WriteAttributeValue("", 429, CultureInfo.CurrentUICulture.TwoLetterISOLanguageName, 429, 54, false);
+
+#line default
+#line hidden
+            EndWriteAttribute();
+            WriteLiteral(" xmlns=\"http://www.w3.org/1999/xhtml\">\r\n    <head>\r\n        <meta charset=\"utf-8\" />\r\n        <title>");
+#line 25 "ErrorPage.cshtml"
+          Write(Resources.ErrorPageHtml_Title);
+
+#line default
+#line hidden
+            WriteLiteral(@"</title>
+        <style>
+            body {
+    font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;
+    font-size: .813em;
+    color: #222;
+}
+
+h1, h2, h3, h4, h5 {
+    /*font-family: 'Segoe UI',Tahoma,Arial,Helvetica,sans-serif;*/
+    font-weight: 100;
+}
+
+h1 {
+    color: #44525e;
+    margin: 15px 0 15px 0;
+}
+
+h2 {
+    margin: 10px 5px 0 0;
+}
+
+h3 {
+    color: #363636;
+    margin: 5px 5px 0 0;
+}
+
+code {
+    font-family: Consolas, ""Courier New"", courier, monospace;
+}
+
+body .titleerror {
+    padding: 3px 3px 6px 3px;
+    display: block;
+    font-size: 1.5em;
+    font-weight: 100;
+}
+
+body .location {
+    margin: 3px 0 10px 30px;
+}
+
+#header {
+    font-size: 18px;
+    padding: 15px 0;
+    border-top: 1px #ddd solid;
+    border-bottom: 1px #ddd solid;
+    margin-bottom: 0;
+}
+
+    #header li {
+        display: inline;
+        margin: 5px;
+        padding: 5px;
+        color: #a0a0a0;
+        cursor: pointer;
+    }
+
+    #header .selected {
+        ba");
+            WriteLiteral(@"ckground: #44c5f2;
+        color: #fff;
+    }
+
+#stackpage ul {
+    list-style: none;
+    padding-left: 0;
+    margin: 0;
+    /*border-bottom: 1px #ddd solid;*/
+}
+
+#stackpage .details {
+    font-size: 1.2em;
+    padding: 3px;
+    color: #000;
+}
+
+#stackpage .stackerror {
+    padding: 5px;
+    border-bottom: 1px #ddd solid;
+}
+
+
+#stackpage .frame {
+    padding: 0;
+    margin: 0 0 0 30px;
+}
+
+    #stackpage .frame h3 {
+        padding: 2px;
+        margin: 0;
+    }
+
+#stackpage .source {
+    padding: 0 0 0 30px;
+}
+
+    #stackpage .source ol li {
+        font-family: Consolas, ""Courier New"", courier, monospace;
+        white-space: pre;
+        background-color: #fbfbfb;
+    }
+
+#stackpage .frame .source .highlight li span {
+    color: #FF0000;
+}
+
+#stackpage .source ol.collapsible li {
+    color: #888;
+}
+
+    #stackpage .source ol.collapsible li span {
+        color: #606060;
+    }
+
+.page table {
+    border-collapse: separate;
+    border-spacing: 0;
+    margin:");
+            WriteLiteral(@" 0 0 20px;
+}
+
+.page th {
+    vertical-align: bottom;
+    padding: 10px 5px 5px 5px;
+    font-weight: 400;
+    color: #a0a0a0;
+    text-align: left;
+}
+
+.page td {
+    padding: 3px 10px;
+}
+
+.page th, .page td {
+    border-right: 1px #ddd solid;
+    border-bottom: 1px #ddd solid;
+    border-left: 1px transparent solid;
+    border-top: 1px transparent solid;
+    box-sizing: border-box;
+}
+
+    .page th:last-child, .page td:last-child {
+        border-right: 1px transparent solid;
+    }
+
+.page .length {
+    text-align: right;
+}
+
+a {
+    color: #1ba1e2;
+    text-decoration: none;
+}
+
+    a:hover {
+        color: #13709e;
+        text-decoration: underline;
+    }
+
+.showRawException {
+    cursor: pointer;
+    color: #44c5f2;
+    background-color: transparent;
+    font-size: 1.2em;
+    text-align: left;
+    text-decoration: none;
+    display: inline-block;
+    border: 0;
+    padding: 0;
+}
+
+.rawExceptionStackTrace {
+    font-size: 1.2em;
+}
+
+.rawExceptionBlock {
+  ");
+            WriteLiteral(@"  border-top: 1px #ddd solid;
+    border-bottom: 1px #ddd solid;
+}
+
+.showRawExceptionContainer {
+    margin-top: 10px;
+    margin-bottom: 10px;
+}
+
+.expandCollapseButton {
+    cursor: pointer;
+    float: left;
+    height: 16px;
+    width: 16px;
+    font-size: 10px;
+    position: absolute;
+    left: 10px;
+    background-color: #eee;
+    padding: 0;
+    border: 0;
+    margin: 0;
+}
+
+        </style>
+    </head>
+    <body>
+        <h1>");
+#line 226 "ErrorPage.cshtml"
+       Write(Resources.ErrorPageHtml_UnhandledException);
+
+#line default
+#line hidden
+            WriteLiteral("</h1>\r\n");
+#line 227 "ErrorPage.cshtml"
+        
+
+#line default
+#line hidden
+
+#line 227 "ErrorPage.cshtml"
+         foreach (var errorDetail in Model.ErrorDetails)
+        {
+
+#line default
+#line hidden
+
+            WriteLiteral("            <div class=\"titleerror\">");
+#line 229 "ErrorPage.cshtml"
+                               Write(errorDetail.Error.GetType().Name);
+
+#line default
+#line hidden
+            WriteLiteral(": ");
+#line 229 "ErrorPage.cshtml"
+                                                                          Output.Write(HtmlEncodeAndReplaceLineBreaks(errorDetail.Error.Message)); 
+
+#line default
+#line hidden
+
+            WriteLiteral("</div>\r\n");
+#line 230 "ErrorPage.cshtml"
+            
+
+#line default
+#line hidden
+
+#line 230 "ErrorPage.cshtml"
+              
+                var firstFrame = errorDetail.StackFrames.FirstOrDefault();
+                if (firstFrame != null)
+                {
+                    location = firstFrame.Function;
+                }
+            
+
+#line default
+#line hidden
+
+#line 236 "ErrorPage.cshtml"
+             
+            if (!string.IsNullOrEmpty(location) && firstFrame != null && !string.IsNullOrEmpty(firstFrame.File))
+            {
+
+#line default
+#line hidden
+
+            WriteLiteral("                <p class=\"location\">");
+#line 239 "ErrorPage.cshtml"
+                               Write(location);
+
+#line default
+#line hidden
+            WriteLiteral(" in <code");
+            BeginWriteAttribute("title", " title=\"", 4844, "\"", 4868, 1);
+#line 239 "ErrorPage.cshtml"
+WriteAttributeValue("", 4852, firstFrame.File, 4852, 16, false);
+
+#line default
+#line hidden
+            EndWriteAttribute();
+            WriteLiteral(">");
+#line 239 "ErrorPage.cshtml"
+                                                                           Write(System.IO.Path.GetFileName(firstFrame.File));
+
+#line default
+#line hidden
+            WriteLiteral("</code>, line ");
+#line 239 "ErrorPage.cshtml"
+                                                                                                                                     Write(firstFrame.Line);
+
+#line default
+#line hidden
+            WriteLiteral("</p>\r\n");
+#line 240 "ErrorPage.cshtml"
+            }
+            else if (!string.IsNullOrEmpty(location))
+            {
+
+#line default
+#line hidden
+
+            WriteLiteral("                <p class=\"location\">");
+#line 243 "ErrorPage.cshtml"
+                               Write(location);
+
+#line default
+#line hidden
+            WriteLiteral("</p>\r\n");
+#line 244 "ErrorPage.cshtml"
+            }
+            else
+            {
+
+#line default
+#line hidden
+
+            WriteLiteral("                <p class=\"location\">");
+#line 247 "ErrorPage.cshtml"
+                               Write(Resources.ErrorPageHtml_UnknownLocation);
+
+#line default
+#line hidden
+            WriteLiteral("</p>\r\n");
+#line 248 "ErrorPage.cshtml"
+            }
+
+            var reflectionTypeLoadException = errorDetail.Error as ReflectionTypeLoadException;
+            if (reflectionTypeLoadException != null)
+            {
+                if (reflectionTypeLoadException.LoaderExceptions.Length > 0)
+                {
+
+#line default
+#line hidden
+
+            WriteLiteral("                    <h3>Loader Exceptions:</h3>\r\n                    <ul>\r\n");
+#line 257 "ErrorPage.cshtml"
+                        
+
+#line default
+#line hidden
+
+#line 257 "ErrorPage.cshtml"
+                         foreach (var ex in reflectionTypeLoadException.LoaderExceptions)
+                        {
+
+#line default
+#line hidden
+
+            WriteLiteral("                            <li>");
+#line 259 "ErrorPage.cshtml"
+                           Write(ex.Message);
+
+#line default
+#line hidden
+            WriteLiteral("</li>\r\n");
+#line 260 "ErrorPage.cshtml"
+                        }
+
+#line default
+#line hidden
+
+            WriteLiteral("                    </ul>\r\n");
+#line 262 "ErrorPage.cshtml"
+                }
+            }
+        }
+
+#line default
+#line hidden
+
+            WriteLiteral("        <div id=\"stackpage\" class=\"page\">\r\n            <ul>\r\n");
+#line 267 "ErrorPage.cshtml"
+                
+
+#line default
+#line hidden
+
+#line 267 "ErrorPage.cshtml"
+                  
+                    var exceptionCount = 0;
+                    var stackFrameCount = 0;
+                    var exceptionDetailId = "";
+                    var frameId = "";
+                
+
+#line default
+#line hidden
+
+            WriteLiteral("                ");
+#line 273 "ErrorPage.cshtml"
+                 foreach (var errorDetail in Model.ErrorDetails)
+                {
+                    
+
+#line default
+#line hidden
+
+#line 275 "ErrorPage.cshtml"
+                      
+                        exceptionCount++;
+                        exceptionDetailId = "exceptionDetail" + exceptionCount;
+                    
+
+#line default
+#line hidden
+
+#line 278 "ErrorPage.cshtml"
+                     
+
+#line default
+#line hidden
+
+            WriteLiteral("                    <li>\r\n                        <h2 class=\"stackerror\">");
+#line 280 "ErrorPage.cshtml"
+                                          Write(errorDetail.Error.GetType().Name);
+
+#line default
+#line hidden
+            WriteLiteral(": ");
+#line 280 "ErrorPage.cshtml"
+                                                                             Write(errorDetail.Error.Message);
+
+#line default
+#line hidden
+            WriteLiteral("</h2>\r\n                        <ul>\r\n");
+#line 282 "ErrorPage.cshtml"
+                        
+
+#line default
+#line hidden
+
+#line 282 "ErrorPage.cshtml"
+                         foreach (var frame in errorDetail.StackFrames)
+                        {
+                            
+
+#line default
+#line hidden
+
+#line 284 "ErrorPage.cshtml"
+                              
+                                stackFrameCount++;
+                                frameId = "frame" + stackFrameCount;
+                            
+
+#line default
+#line hidden
+
+#line 287 "ErrorPage.cshtml"
+                             
+
+#line default
+#line hidden
+
+            WriteLiteral("                            <li class=\"frame\"");
+            BeginWriteAttribute("id", " id=\"", 6874, "\"", 6887, 1);
+#line 288 "ErrorPage.cshtml"
+WriteAttributeValue("", 6879, frameId, 6879, 8, false);
+
+#line default
+#line hidden
+            EndWriteAttribute();
+            WriteLiteral(">\r\n");
+#line 289 "ErrorPage.cshtml"
+                                
+
+#line default
+#line hidden
+
+#line 289 "ErrorPage.cshtml"
+                                 if (string.IsNullOrEmpty(frame.File))
+                                {
+
+#line default
+#line hidden
+
+            WriteLiteral("                                    <h3>");
+#line 291 "ErrorPage.cshtml"
+                                   Write(frame.Function);
+
+#line default
+#line hidden
+            WriteLiteral("</h3>\r\n");
+#line 292 "ErrorPage.cshtml"
+                                }
+                                else
+                                {
+
+#line default
+#line hidden
+
+            WriteLiteral("                                    <h3>");
+#line 295 "ErrorPage.cshtml"
+                                   Write(frame.Function);
+
+#line default
+#line hidden
+            WriteLiteral(" in <code");
+            BeginWriteAttribute("title", " title=\"", 7232, "\"", 7251, 1);
+#line 295 "ErrorPage.cshtml"
+WriteAttributeValue("", 7240, frame.File, 7240, 11, false);
+
+#line default
+#line hidden
+            EndWriteAttribute();
+            WriteLiteral(">");
+#line 295 "ErrorPage.cshtml"
+                                                                                Write(System.IO.Path.GetFileName(frame.File));
+
+#line default
+#line hidden
+            WriteLiteral("</code></h3>\r\n");
+#line 296 "ErrorPage.cshtml"
+                                }
+
+#line default
+#line hidden
+
+            WriteLiteral("\r\n");
+#line 298 "ErrorPage.cshtml"
+                                
+
+#line default
+#line hidden
+
+#line 298 "ErrorPage.cshtml"
+                                 if (frame.Line != 0 && frame.ContextCode.Any())
+                                {
+
+#line default
+#line hidden
+
+            WriteLiteral("                                    <button class=\"expandCollapseButton\" data-frameId=\"");
+#line 300 "ErrorPage.cshtml"
+                                                                                  Write(frameId);
+
+#line default
+#line hidden
+            WriteLiteral("\">+</button>\r\n                                    <div class=\"source\">\r\n");
+#line 302 "ErrorPage.cshtml"
+                                        
+
+#line default
+#line hidden
+
+#line 302 "ErrorPage.cshtml"
+                                         if (frame.PreContextCode.Any())
+                                        {
+
+#line default
+#line hidden
+
+            WriteLiteral("                                            <ol");
+            BeginWriteAttribute("start", " start=\"", 7791, "\"", 7820, 1);
+#line 304 "ErrorPage.cshtml"
+WriteAttributeValue("", 7799, frame.PreContextLine, 7799, 21, false);
+
+#line default
+#line hidden
+            EndWriteAttribute();
+            WriteLiteral(" class=\"collapsible\">\r\n");
+#line 305 "ErrorPage.cshtml"
+                                                
+
+#line default
+#line hidden
+
+#line 305 "ErrorPage.cshtml"
+                                                 foreach (var line in frame.PreContextCode)
+                                                {
+
+#line default
+#line hidden
+
+            WriteLiteral("                                                    <li><span>");
+#line 307 "ErrorPage.cshtml"
+                                                         Write(line);
+
+#line default
+#line hidden
+            WriteLiteral("</span></li>\r\n");
+#line 308 "ErrorPage.cshtml"
+                                                }
+
+#line default
+#line hidden
+
+            WriteLiteral("                                            </ol>\r\n");
+#line 310 "ErrorPage.cshtml"
+                                        }
+
+#line default
+#line hidden
+
+            WriteLiteral("\r\n                                        <ol");
+            BeginWriteAttribute("start", " start=\"", 8259, "\"", 8278, 1);
+#line 312 "ErrorPage.cshtml"
+WriteAttributeValue("", 8267, frame.Line, 8267, 11, false);
+
+#line default
+#line hidden
+            EndWriteAttribute();
+            WriteLiteral(" class=\"highlight\">\r\n");
+#line 313 "ErrorPage.cshtml"
+                                            
+
+#line default
+#line hidden
+
+#line 313 "ErrorPage.cshtml"
+                                             foreach (var line in frame.ContextCode)
+                                            {
+
+#line default
+#line hidden
+
+            WriteLiteral("                                                <li><span>");
+#line 315 "ErrorPage.cshtml"
+                                                     Write(line);
+
+#line default
+#line hidden
+            WriteLiteral("</span></li>\r\n");
+#line 316 "ErrorPage.cshtml"
+                                            }
+
+#line default
+#line hidden
+
+            WriteLiteral("                                        </ol>\r\n\r\n");
+#line 319 "ErrorPage.cshtml"
+                                        
+
+#line default
+#line hidden
+
+#line 319 "ErrorPage.cshtml"
+                                         if (frame.PostContextCode.Any())
+                                        {
+
+#line default
+#line hidden
+
+            WriteLiteral("                                            <ol");
+            BeginWriteAttribute("start", " start=\'", 8771, "\'", 8796, 1);
+#line 321 "ErrorPage.cshtml"
+WriteAttributeValue("", 8779, frame.Line + 1, 8779, 17, false);
+
+#line default
+#line hidden
+            EndWriteAttribute();
+            WriteLiteral(" class=\"collapsible\">\r\n");
+#line 322 "ErrorPage.cshtml"
+                                                
+
+#line default
+#line hidden
+
+#line 322 "ErrorPage.cshtml"
+                                                 foreach (var line in frame.PostContextCode)
+                                                {
+
+#line default
+#line hidden
+
+            WriteLiteral("                                                    <li><span>");
+#line 324 "ErrorPage.cshtml"
+                                                         Write(line);
+
+#line default
+#line hidden
+            WriteLiteral("</span></li>\r\n");
+#line 325 "ErrorPage.cshtml"
+                                                }
+
+#line default
+#line hidden
+
+            WriteLiteral("                                            </ol>\r\n");
+#line 327 "ErrorPage.cshtml"
+                                        }
+
+#line default
+#line hidden
+
+            WriteLiteral("                                    </div>\r\n");
+#line 329 "ErrorPage.cshtml"
+                                }
+
+#line default
+#line hidden
+
+            WriteLiteral("                            </li>\r\n");
+#line 331 "ErrorPage.cshtml"
+                        }
+
+#line default
+#line hidden
+
+            WriteLiteral(@"                        </ul>
+                    </li>
+                    <li>
+                        <br/>
+                        <div class=""rawExceptionBlock"">
+                            <div class=""showRawExceptionContainer"">
+                                <button class=""showRawException"" data-exceptionDetailId=""");
+#line 338 "ErrorPage.cshtml"
+                                                                                    Write(exceptionDetailId);
+
+#line default
+#line hidden
+            WriteLiteral("\">Show raw exception details</button>\r\n                            </div>\r\n                            <div");
+            BeginWriteAttribute("id", " id=\"", 9787, "\"", 9810, 1);
+#line 340 "ErrorPage.cshtml"
+WriteAttributeValue("", 9792, exceptionDetailId, 9792, 18, false);
+
+#line default
+#line hidden
+            EndWriteAttribute();
+            WriteLiteral(" class=\"rawExceptionDetails\">\r\n                                <pre class=\"rawExceptionStackTrace\">");
+#line 341 "ErrorPage.cshtml"
+                                                               Write(errorDetail.Error.ToString());
+
+#line default
+#line hidden
+            WriteLiteral("</pre>\r\n                            </div>\r\n                        </div>\r\n                    </li>\r\n");
+#line 345 "ErrorPage.cshtml"
+                }
+
+#line default
+#line hidden
+
+            WriteLiteral("            </ul>\r\n        </div>\r\n        <footer>\r\n            ");
+#line 349 "ErrorPage.cshtml"
+       Write(Model.RuntimeDisplayName);
+
+#line default
+#line hidden
+            WriteLiteral(" ");
+#line 349 "ErrorPage.cshtml"
+                                 Write(Model.RuntimeArchitecture);
+
+#line default
+#line hidden
+            WriteLiteral(" v");
+#line 349 "ErrorPage.cshtml"
+                                                              Write(Model.ClrVersion);
+
+#line default
+#line hidden
+            WriteLiteral(" &nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;Microsoft.AspNetCore.Hosting version ");
+#line 349 "ErrorPage.cshtml"
+                                                                                                                                                           Write(Model.CurrentAssemblyVesion);
+
+#line default
+#line hidden
+            WriteLiteral(" &nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp; ");
+#line 349 "ErrorPage.cshtml"
+                                                                                                                                                                                                                              Write(Model.OperatingSystemDescription);
+
+#line default
+#line hidden
+            WriteLiteral(@" &nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;<a href=""http://go.microsoft.com/fwlink/?LinkId=517394"">Need help?</a>
+        </footer>
+        <script>
+            //<!--
+            (function (window, undefined) {
+    ""use strict"";
+
+    function ns(selector, element) {
+        return new NodeCollection(selector, element);
+    }
+
+    function NodeCollection(selector, element) {
+        this.items = [];
+        element = element || window.document;
+
+        var nodeList;
+
+        if (typeof (selector) === ""string"") {
+            nodeList = element.querySelectorAll(selector);
+            for (var i = 0, l = nodeList.length; i < l; i++) {
+                this.items.push(nodeList.item(i));
+            }
+        }
+    }
+
+    NodeCollection.prototype = {
+        each: function (callback) {
+            for (var i = 0, l = this.items.length; i < l; i++) {
+                callback(this.items[i], i);
+            }
+            return this;
+        },
+
+        children: function (selector) {
+   ");
+            WriteLiteral(@"         var children = [];
+
+            this.each(function (el) {
+                children = children.concat(ns(selector, el).items);
+            });
+
+            return ns(children);
+        },
+
+        hide: function () {
+            this.each(function (el) {
+                el.style.display = ""none"";
+            });
+
+            return this;
+        },
+
+        toggle: function () {
+            this.each(function (el) {
+                el.style.display = el.style.display === ""none"" ? """" : ""none"";
+            });
+
+            return this;
+        },
+
+        show: function () {
+            this.each(function (el) {
+                el.style.display = """";
+            });
+
+            return this;
+        },
+
+        addClass: function (className) {
+            this.each(function (el) {
+                var existingClassName = el.className,
+                    classNames;
+                if (!existingClassName) {
+                    el.className = className;
+             ");
+            WriteLiteral(@"   } else {
+                    classNames = existingClassName.split("" "");
+                    if (classNames.indexOf(className) < 0) {
+                        el.className = existingClassName + "" "" + className;
+                    }
+                }
+            });
+
+            return this;
+        },
+
+        removeClass: function (className) {
+            this.each(function (el) {
+                var existingClassName = el.className,
+                    classNames, index;
+                if (existingClassName === className) {
+                    el.className = """";
+                } else if (existingClassName) {
+                    classNames = existingClassName.split("" "");
+                    index = classNames.indexOf(className);
+                    if (index > 0) {
+                        classNames.splice(index, 1);
+                        el.className = classNames.join("" "");
+                    }
+                }
+            });
+
+            return this;
+        },
+
+    ");
+            WriteLiteral(@"    attr: function (name) {
+            if (this.items.length === 0) {
+                return null;
+            }
+
+            return this.items[0].getAttribute(name);
+        },
+
+        on: function (eventName, handler) {
+            this.each(function (el, idx) {
+                var callback = function (e) {
+                    e = e || window.event;
+                    if (!e.which && e.keyCode) {
+                        e.which = e.keyCode; // Normalize IE8 key events
+                    }
+                    handler.apply(el, [e]);
+                };
+
+                if (el.addEventListener) { // DOM Events
+                    el.addEventListener(eventName, callback, false);
+                } else if (el.attachEvent) { // IE8 events
+                    el.attachEvent(""on"" + eventName, callback);
+                } else {
+                    el[""on"" + type] = callback;
+                }
+            });
+
+            return this;
+        },
+
+        click: function (handler) {
");
+            WriteLiteral(@"
+            return this.on(""click"", handler);
+        },
+
+        keypress: function (handler) {
+            return this.on(""keypress"", handler);
+        }
+    };
+
+    function frame(el) {
+        ns("".source .collapsible"", el).toggle();
+    }
+
+    function expandCollapseButton(el) {
+        var frameId = el.getAttribute(""data-frameId"");
+        frame(document.getElementById(frameId));
+        if (el.innerText === ""+"") {
+            el.innerText = ""-"";
+        }
+        else {
+            el.innerText = ""+"";
+        }
+    }
+
+    function tab(el) {
+        var unselected = ns(""#header .selected"").removeClass(""selected"").attr(""id"");
+        var selected = ns(""#"" + el.id).addClass(""selected"").attr(""id"");
+
+        ns(""#"" + unselected + ""page"").hide();
+        ns(""#"" + selected + ""page"").show();
+    }
+
+    ns("".rawExceptionDetails"").hide();
+    ns("".collapsible"").hide();
+    ns("".page"").hide();
+    ns(""#stackpage"").show();
+
+    ns("".expandCollapseButton"")
+        .click(functi");
+            WriteLiteral(@"on () {
+            expandCollapseButton(this);
+        })
+        .keypress(function (e) {
+            if (e.which === 13) {
+                expandCollapseButton(this);
+            }
+        });
+
+    ns(""#header li"")
+        .click(function () {
+            tab(this);
+        })
+        .keypress(function (e) {
+            if (e.which === 13) {
+                tab(this);
+            }
+        });
+
+    ns("".showRawException"")
+        .click(function () {
+            var exceptionDetailId = this.getAttribute(""data-exceptionDetailId"");
+            ns(""#"" + exceptionDetailId).toggle();
+        });
+})(window);
+            //-->
+        </script>
+</body>
+</html>
+");
+        }
+        #pragma warning restore 1998
+    }
+}
diff --git a/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.cshtml b/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.cshtml
new file mode 100644
index 0000000000000000000000000000000000000000..8f8360c565f873fddd87dc70eb4ebb6f93fa6b36
--- /dev/null
+++ b/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.cshtml
@@ -0,0 +1,162 @@
+@using System
+@using System.Globalization
+@using System.Linq
+@using System.Net
+@using System.Reflection
+@using Microsoft.AspNetCore.Hosting.Views
+
+@functions
+{
+    public ErrorPage(ErrorPageModel model)
+    {
+        Model = model;
+    }
+
+    public ErrorPageModel Model { get; set; }
+}
+@{
+    Response.ContentType = "text/html; charset=utf-8";
+    var location = string.Empty;
+}
+<!DOCTYPE html>
+<html lang="@CultureInfo.CurrentUICulture.TwoLetterISOLanguageName" xmlns="http://www.w3.org/1999/xhtml">
+    <head>
+        <meta charset="utf-8" />
+        <title>@Resources.ErrorPageHtml_Title</title>
+        <style>
+            <%$ include: ErrorPage.css %>
+        </style>
+    </head>
+    <body>
+        <h1>@Resources.ErrorPageHtml_UnhandledException</h1>
+        @foreach (var errorDetail in Model.ErrorDetails)
+        {
+            <div class="titleerror">@errorDetail.Error.GetType().Name: @{ Output.Write(HtmlEncodeAndReplaceLineBreaks(errorDetail.Error.Message)); }</div>
+            @{
+                var firstFrame = errorDetail.StackFrames.FirstOrDefault();
+                if (firstFrame != null)
+                {
+                    location = firstFrame.Function;
+                }
+            }
+            if (!string.IsNullOrEmpty(location) && firstFrame != null && !string.IsNullOrEmpty(firstFrame.File))
+            {
+                <p class="location">@location in <code title="@firstFrame.File">@System.IO.Path.GetFileName(firstFrame.File)</code>, line @firstFrame.Line</p>
+            }
+            else if (!string.IsNullOrEmpty(location))
+            {
+                <p class="location">@location</p>
+            }
+            else
+            {
+                <p class="location">@Resources.ErrorPageHtml_UnknownLocation</p>
+            }
+
+            var reflectionTypeLoadException = errorDetail.Error as ReflectionTypeLoadException;
+            if (reflectionTypeLoadException != null)
+            {
+                if (reflectionTypeLoadException.LoaderExceptions.Length > 0)
+                {
+                    <h3>Loader Exceptions:</h3>
+                    <ul>
+                        @foreach (var ex in reflectionTypeLoadException.LoaderExceptions)
+                        {
+                            <li>@ex.Message</li>
+                        }
+                    </ul>
+                }
+            }
+        }
+        <div id="stackpage" class="page">
+            <ul>
+                @{
+                    var exceptionCount = 0;
+                    var stackFrameCount = 0;
+                    var exceptionDetailId = "";
+                    var frameId = "";
+                }
+                @foreach (var errorDetail in Model.ErrorDetails)
+                {
+                    @{
+                        exceptionCount++;
+                        exceptionDetailId = "exceptionDetail" + exceptionCount;
+                    }
+                    <li>
+                        <h2 class="stackerror">@errorDetail.Error.GetType().Name: @errorDetail.Error.Message</h2>
+                        <ul>
+                        @foreach (var frame in errorDetail.StackFrames)
+                        {
+                            @{
+                                stackFrameCount++;
+                                frameId = "frame" + stackFrameCount;
+                            }
+                            <li class="frame" id="@frameId">
+                                @if (string.IsNullOrEmpty(frame.File))
+                                {
+                                    <h3>@frame.Function</h3>
+                                }
+                                else
+                                {
+                                    <h3>@frame.Function in <code title="@frame.File">@System.IO.Path.GetFileName(frame.File)</code></h3>
+                                }
+
+                                @if (frame.Line != 0 && frame.ContextCode.Any())
+                                {
+                                    <button class="expandCollapseButton" data-frameId="@frameId">+</button>
+                                    <div class="source">
+                                        @if (frame.PreContextCode.Any())
+                                        {
+                                            <ol start="@frame.PreContextLine" class="collapsible">
+                                                @foreach (var line in frame.PreContextCode)
+                                                {
+                                                    <li><span>@line</span></li>
+                                                }
+                                            </ol>
+                                        }
+
+                                        <ol start="@frame.Line" class="highlight">
+                                            @foreach (var line in frame.ContextCode)
+                                            {
+                                                <li><span>@line</span></li>
+                                            }
+                                        </ol>
+
+                                        @if (frame.PostContextCode.Any())
+                                        {
+                                            <ol start='@(frame.Line + 1)' class="collapsible">
+                                                @foreach (var line in frame.PostContextCode)
+                                                {
+                                                    <li><span>@line</span></li>
+                                                }
+                                            </ol>
+                                        }
+                                    </div>
+                                }
+                            </li>
+                        }
+                        </ul>
+                    </li>
+                    <li>
+                        <br/>
+                        <div class="rawExceptionBlock">
+                            <div class="showRawExceptionContainer">
+                                <button class="showRawException" data-exceptionDetailId="@exceptionDetailId">Show raw exception details</button>
+                            </div>
+                            <div id="@exceptionDetailId" class="rawExceptionDetails">
+                                <pre class="rawExceptionStackTrace">@errorDetail.Error.ToString()</pre>
+                            </div>
+                        </div>
+                    </li>
+                }
+            </ul>
+        </div>
+        <footer>
+            @Model.RuntimeDisplayName @Model.RuntimeArchitecture v@(Model.ClrVersion) &nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;Microsoft.AspNetCore.Hosting version @Model.CurrentAssemblyVesion &nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp; @Model.OperatingSystemDescription &nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;<a href="http://go.microsoft.com/fwlink/?LinkId=517394">Need help?</a>
+        </footer>
+        <script>
+            //<!--
+            <%$ include: ErrorPage.js %>
+            //-->
+        </script>
+</body>
+</html>
diff --git a/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.css b/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.css
new file mode 100644
index 0000000000000000000000000000000000000000..4d3287c12dd6c08e026d617358b17c1f3fefb1d1
--- /dev/null
+++ b/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.css
@@ -0,0 +1,195 @@
+body {
+    font-family: 'Segoe UI', Tahoma, Arial, Helvetica, sans-serif;
+    font-size: .813em;
+    color: #222;
+}
+
+h1, h2, h3, h4, h5 {
+    /*font-family: 'Segoe UI',Tahoma,Arial,Helvetica,sans-serif;*/
+    font-weight: 100;
+}
+
+h1 {
+    color: #44525e;
+    margin: 15px 0 15px 0;
+}
+
+h2 {
+    margin: 10px 5px 0 0;
+}
+
+h3 {
+    color: #363636;
+    margin: 5px 5px 0 0;
+}
+
+code {
+    font-family: Consolas, "Courier New", courier, monospace;
+}
+
+body .titleerror {
+    padding: 3px 3px 6px 3px;
+    display: block;
+    font-size: 1.5em;
+    font-weight: 100;
+}
+
+body .location {
+    margin: 3px 0 10px 30px;
+}
+
+#header {
+    font-size: 18px;
+    padding: 15px 0;
+    border-top: 1px #ddd solid;
+    border-bottom: 1px #ddd solid;
+    margin-bottom: 0;
+}
+
+    #header li {
+        display: inline;
+        margin: 5px;
+        padding: 5px;
+        color: #a0a0a0;
+        cursor: pointer;
+    }
+
+    #header .selected {
+        background: #44c5f2;
+        color: #fff;
+    }
+
+#stackpage ul {
+    list-style: none;
+    padding-left: 0;
+    margin: 0;
+    /*border-bottom: 1px #ddd solid;*/
+}
+
+#stackpage .details {
+    font-size: 1.2em;
+    padding: 3px;
+    color: #000;
+}
+
+#stackpage .stackerror {
+    padding: 5px;
+    border-bottom: 1px #ddd solid;
+}
+
+
+#stackpage .frame {
+    padding: 0;
+    margin: 0 0 0 30px;
+}
+
+    #stackpage .frame h3 {
+        padding: 2px;
+        margin: 0;
+    }
+
+#stackpage .source {
+    padding: 0 0 0 30px;
+}
+
+    #stackpage .source ol li {
+        font-family: Consolas, "Courier New", courier, monospace;
+        white-space: pre;
+        background-color: #fbfbfb;
+    }
+
+#stackpage .frame .source .highlight li span {
+    color: #FF0000;
+}
+
+#stackpage .source ol.collapsible li {
+    color: #888;
+}
+
+    #stackpage .source ol.collapsible li span {
+        color: #606060;
+    }
+
+.page table {
+    border-collapse: separate;
+    border-spacing: 0;
+    margin: 0 0 20px;
+}
+
+.page th {
+    vertical-align: bottom;
+    padding: 10px 5px 5px 5px;
+    font-weight: 400;
+    color: #a0a0a0;
+    text-align: left;
+}
+
+.page td {
+    padding: 3px 10px;
+}
+
+.page th, .page td {
+    border-right: 1px #ddd solid;
+    border-bottom: 1px #ddd solid;
+    border-left: 1px transparent solid;
+    border-top: 1px transparent solid;
+    box-sizing: border-box;
+}
+
+    .page th:last-child, .page td:last-child {
+        border-right: 1px transparent solid;
+    }
+
+.page .length {
+    text-align: right;
+}
+
+a {
+    color: #1ba1e2;
+    text-decoration: none;
+}
+
+    a:hover {
+        color: #13709e;
+        text-decoration: underline;
+    }
+
+.showRawException {
+    cursor: pointer;
+    color: #44c5f2;
+    background-color: transparent;
+    font-size: 1.2em;
+    text-align: left;
+    text-decoration: none;
+    display: inline-block;
+    border: 0;
+    padding: 0;
+}
+
+.rawExceptionStackTrace {
+    font-size: 1.2em;
+}
+
+.rawExceptionBlock {
+    border-top: 1px #ddd solid;
+    border-bottom: 1px #ddd solid;
+}
+
+.showRawExceptionContainer {
+    margin-top: 10px;
+    margin-bottom: 10px;
+}
+
+.expandCollapseButton {
+    cursor: pointer;
+    float: left;
+    height: 16px;
+    width: 16px;
+    font-size: 10px;
+    position: absolute;
+    left: 10px;
+    background-color: #eee;
+    padding: 0;
+    border: 0;
+    margin: 0;
+}
diff --git a/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.js b/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.js
new file mode 100644
index 0000000000000000000000000000000000000000..3925cfd2f2759d2b97fe92276504a28dc5812985
--- /dev/null
+++ b/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.js
@@ -0,0 +1,192 @@
+(function (window, undefined) {
+    "use strict";
+
+    function ns(selector, element) {
+        return new NodeCollection(selector, element);
+    }
+
+    function NodeCollection(selector, element) {
+        this.items = [];
+        element = element || window.document;
+
+        var nodeList;
+
+        if (typeof (selector) === "string") {
+            nodeList = element.querySelectorAll(selector);
+            for (var i = 0, l = nodeList.length; i < l; i++) {
+                this.items.push(nodeList.item(i));
+            }
+        }
+    }
+
+    NodeCollection.prototype = {
+        each: function (callback) {
+            for (var i = 0, l = this.items.length; i < l; i++) {
+                callback(this.items[i], i);
+            }
+            return this;
+        },
+
+        children: function (selector) {
+            var children = [];
+
+            this.each(function (el) {
+                children = children.concat(ns(selector, el).items);
+            });
+
+            return ns(children);
+        },
+
+        hide: function () {
+            this.each(function (el) {
+                el.style.display = "none";
+            });
+
+            return this;
+        },
+
+        toggle: function () {
+            this.each(function (el) {
+                el.style.display = el.style.display === "none" ? "" : "none";
+            });
+
+            return this;
+        },
+
+        show: function () {
+            this.each(function (el) {
+                el.style.display = "";
+            });
+
+            return this;
+        },
+
+        addClass: function (className) {
+            this.each(function (el) {
+                var existingClassName = el.className,
+                    classNames;
+                if (!existingClassName) {
+                    el.className = className;
+                } else {
+                    classNames = existingClassName.split(" ");
+                    if (classNames.indexOf(className) < 0) {
+                        el.className = existingClassName + " " + className;
+                    }
+                }
+            });
+
+            return this;
+        },
+
+        removeClass: function (className) {
+            this.each(function (el) {
+                var existingClassName = el.className,
+                    classNames, index;
+                if (existingClassName === className) {
+                    el.className = "";
+                } else if (existingClassName) {
+                    classNames = existingClassName.split(" ");
+                    index = classNames.indexOf(className);
+                    if (index > 0) {
+                        classNames.splice(index, 1);
+                        el.className = classNames.join(" ");
+                    }
+                }
+            });
+
+            return this;
+        },
+
+        attr: function (name) {
+            if (this.items.length === 0) {
+                return null;
+            }
+
+            return this.items[0].getAttribute(name);
+        },
+
+        on: function (eventName, handler) {
+            this.each(function (el, idx) {
+                var callback = function (e) {
+                    e = e || window.event;
+                    if (!e.which && e.keyCode) {
+                        e.which = e.keyCode; // Normalize IE8 key events
+                    }
+                    handler.apply(el, [e]);
+                };
+
+                if (el.addEventListener) { // DOM Events
+                    el.addEventListener(eventName, callback, false);
+                } else if (el.attachEvent) { // IE8 events
+                    el.attachEvent("on" + eventName, callback);
+                } else {
+                    el["on" + type] = callback;
+                }
+            });
+
+            return this;
+        },
+
+        click: function (handler) {
+            return this.on("click", handler);
+        },
+
+        keypress: function (handler) {
+            return this.on("keypress", handler);
+        }
+    };
+
+    function frame(el) {
+        ns(".source .collapsible", el).toggle();
+    }
+
+    function expandCollapseButton(el) {
+        var frameId = el.getAttribute("data-frameId");
+        frame(document.getElementById(frameId));
+        if (el.innerText === "+") {
+            el.innerText = "-";
+        }
+        else {
+            el.innerText = "+";
+        }
+    }
+
+    function tab(el) {
+        var unselected = ns("#header .selected").removeClass("selected").attr("id");
+        var selected = ns("#" + el.id).addClass("selected").attr("id");
+
+        ns("#" + unselected + "page").hide();
+        ns("#" + selected + "page").show();
+    }
+
+    ns(".rawExceptionDetails").hide();
+    ns(".collapsible").hide();
+    ns(".page").hide();
+    ns("#stackpage").show();
+
+    ns(".expandCollapseButton")
+        .click(function () {
+            expandCollapseButton(this);
+        })
+        .keypress(function (e) {
+            if (e.which === 13) {
+                expandCollapseButton(this);
+            }
+        });
+
+    ns("#header li")
+        .click(function () {
+            tab(this);
+        })
+        .keypress(function (e) {
+            if (e.which === 13) {
+                tab(this);
+            }
+        });
+
+    ns(".showRawException")
+        .click(function () {
+            var exceptionDetailId = this.getAttribute("data-exceptionDetailId");
+            ns("#" + exceptionDetailId).toggle();
+        });
+})(window);
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPageModel.cs b/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPageModel.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b0a9f0354a4c88b8c43d3ec94eb80e8dd1facd5a
--- /dev/null
+++ b/src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPageModel.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.Collections.Generic;
+using Microsoft.Extensions.StackTrace.Sources;
+
+namespace Microsoft.AspNetCore.Hosting.Views
+{
+    /// <summary>
+    /// Holds data to be displayed on the error page.
+    /// </summary>
+    internal class ErrorPageModel
+    {
+        /// <summary>
+        /// Detailed information about each exception in the stack.
+        /// </summary>
+        public IEnumerable<ExceptionDetails> ErrorDetails { get; set; }
+
+        public string RuntimeDisplayName { get; set; }
+
+        public string RuntimeArchitecture { get; set; }
+
+        public string ClrVersion { get; set; }
+
+        public string CurrentAssemblyVesion { get; set; }
+
+        public string OperatingSystemDescription { get; set; }
+    }
+}
diff --git a/src/Hosting/Hosting/src/Startup/StartupBase.cs b/src/Hosting/Hosting/src/Startup/StartupBase.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5a180ba9b422005016138acf9fbf6ff6fb6254a4
--- /dev/null
+++ b/src/Hosting/Hosting/src/Startup/StartupBase.cs
@@ -0,0 +1,50 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    public abstract class StartupBase : IStartup
+    {
+        public abstract void Configure(IApplicationBuilder app);
+
+        IServiceProvider IStartup.ConfigureServices(IServiceCollection services)
+        {
+            ConfigureServices(services);
+            return CreateServiceProvider(services);
+        }
+
+        public virtual void ConfigureServices(IServiceCollection services)
+        {
+        }
+
+        public virtual IServiceProvider CreateServiceProvider(IServiceCollection services)
+        {
+            return services.BuildServiceProvider();
+        }
+    }
+
+    public abstract class StartupBase<TBuilder> : StartupBase
+    {
+        private readonly IServiceProviderFactory<TBuilder> _factory;
+
+        public StartupBase(IServiceProviderFactory<TBuilder> factory)
+        {
+            _factory = factory;
+        }
+
+        public override IServiceProvider CreateServiceProvider(IServiceCollection services)
+        {
+            var builder = _factory.CreateBuilder(services);
+            ConfigureContainer(builder);
+            return _factory.CreateServiceProvider(builder);
+        }
+
+        public virtual void ConfigureContainer(TBuilder builder)
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/WebHostBuilder.cs b/src/Hosting/Hosting/src/WebHostBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..423b898cec5afd3a23625ba2baada19f85160869
--- /dev/null
+++ b/src/Hosting/Hosting/src/WebHostBuilder.cs
@@ -0,0 +1,364 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.ExceptionServices;
+using Microsoft.AspNetCore.Hosting.Builder;
+using Microsoft.AspNetCore.Hosting.Internal;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.ObjectPool;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    /// <summary>
+    /// A builder for <see cref="IWebHost"/>
+    /// </summary>
+    public class WebHostBuilder : IWebHostBuilder
+    {
+        private readonly HostingEnvironment _hostingEnvironment;
+        private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates;
+
+        private IConfiguration _config;
+        private WebHostOptions _options;
+        private WebHostBuilderContext _context;
+        private bool _webHostBuilt;
+        private List<Action<WebHostBuilderContext, IConfigurationBuilder>> _configureAppConfigurationBuilderDelegates;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="WebHostBuilder"/> class.
+        /// </summary>
+        public WebHostBuilder()
+        {
+            _hostingEnvironment = new HostingEnvironment();
+            _configureServicesDelegates = new List<Action<WebHostBuilderContext, IServiceCollection>>();
+            _configureAppConfigurationBuilderDelegates = new List<Action<WebHostBuilderContext, IConfigurationBuilder>>();
+
+            _config = new ConfigurationBuilder()
+                .AddEnvironmentVariables(prefix: "ASPNETCORE_")
+                .Build();
+
+            if (string.IsNullOrEmpty(GetSetting(WebHostDefaults.EnvironmentKey)))
+            {
+                // Try adding legacy environment keys, never remove these.
+                UseSetting(WebHostDefaults.EnvironmentKey, Environment.GetEnvironmentVariable("Hosting:Environment")
+                    ?? Environment.GetEnvironmentVariable("ASPNET_ENV"));
+            }
+
+            if (string.IsNullOrEmpty(GetSetting(WebHostDefaults.ServerUrlsKey)))
+            {
+                // Try adding legacy url key, never remove this.
+                UseSetting(WebHostDefaults.ServerUrlsKey, Environment.GetEnvironmentVariable("ASPNETCORE_SERVER.URLS"));
+            }
+
+            _context = new WebHostBuilderContext
+            {
+                Configuration = _config
+            };
+        }
+
+        /// <summary>
+        /// Get the setting value from the configuration.
+        /// </summary>
+        /// <param name="key">The key of the setting to look up.</param>
+        /// <returns>The value the setting currently contains.</returns>
+        public string GetSetting(string key)
+        {
+            return _config[key];
+        }
+
+        /// <summary>
+        /// Add or replace a setting in the configuration.
+        /// </summary>
+        /// <param name="key">The key of the setting to add or replace.</param>
+        /// <param name="value">The value of the setting to add or replace.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public IWebHostBuilder UseSetting(string key, string value)
+        {
+            _config[key] = value;
+            return this;
+        }
+
+        /// <summary>
+        /// Adds a delegate for configuring additional services for the host or web application. This may be called
+        /// multiple times.
+        /// </summary>
+        /// <param name="configureServices">A delegate for configuring the <see cref="IServiceCollection"/>.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices)
+        {
+            if (configureServices == null)
+            {
+                throw new ArgumentNullException(nameof(configureServices));
+            }
+
+            return ConfigureServices((_, services) => configureServices(services));
+        }
+
+        /// <summary>
+        /// Adds a delegate for configuring additional services for the host or web application. This may be called
+        /// multiple times.
+        /// </summary>
+        /// <param name="configureServices">A delegate for configuring the <see cref="IServiceCollection"/>.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices)
+        {
+            if (configureServices == null)
+            {
+                throw new ArgumentNullException(nameof(configureServices));
+            }
+
+            _configureServicesDelegates.Add(configureServices);
+            return this;
+        }
+
+        /// <summary>
+        /// Adds a delegate for configuring the <see cref="IConfigurationBuilder"/> that will construct an <see cref="IConfiguration"/>.
+        /// </summary>
+        /// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder" /> that will be used to construct an <see cref="IConfiguration" />.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        /// <remarks>
+        /// The <see cref="IConfiguration"/> and <see cref="ILoggerFactory"/> on the <see cref="WebHostBuilderContext"/> are uninitialized at this stage.
+        /// The <see cref="IConfigurationBuilder"/> is pre-populated with the settings of the <see cref="IWebHostBuilder"/>.
+        /// </remarks>
+        public IWebHostBuilder ConfigureAppConfiguration(Action<WebHostBuilderContext, IConfigurationBuilder> configureDelegate)
+        {
+            if (configureDelegate == null)
+            {
+                throw new ArgumentNullException(nameof(configureDelegate));
+            }
+
+            _configureAppConfigurationBuilderDelegates.Add(configureDelegate);
+            return this;
+        }
+
+        /// <summary>
+        /// Builds the required services and an <see cref="IWebHost"/> which hosts a web application.
+        /// </summary>
+        public IWebHost Build()
+        {
+            if (_webHostBuilt)
+            {
+                throw new InvalidOperationException(Resources.WebHostBuilder_SingleInstance);
+            }
+            _webHostBuilt = true;
+
+            var hostingServices = BuildCommonServices(out var hostingStartupErrors);
+            var applicationServices = hostingServices.Clone();
+            var hostingServiceProvider = GetProviderFromFactory(hostingServices);
+
+            if (!_options.SuppressStatusMessages)
+            {
+                // Warn about deprecated environment variables
+                if (Environment.GetEnvironmentVariable("Hosting:Environment") != null)
+                {
+                    Console.WriteLine("The environment variable 'Hosting:Environment' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'");
+                }
+
+                if (Environment.GetEnvironmentVariable("ASPNET_ENV") != null)
+                {
+                    Console.WriteLine("The environment variable 'ASPNET_ENV' is obsolete and has been replaced with 'ASPNETCORE_ENVIRONMENT'");
+                }
+
+                if (Environment.GetEnvironmentVariable("ASPNETCORE_SERVER.URLS") != null)
+                {
+                    Console.WriteLine("The environment variable 'ASPNETCORE_SERVER.URLS' is obsolete and has been replaced with 'ASPNETCORE_URLS'");
+                }
+            }
+
+            var logger = hostingServiceProvider.GetRequiredService<ILogger<WebHost>>();
+            // Warn about duplicate HostingStartupAssemblies
+            foreach (var assemblyName in _options.GetFinalHostingStartupAssemblies().GroupBy(a => a, StringComparer.OrdinalIgnoreCase).Where(g => g.Count() > 1))
+            {
+                logger.LogWarning($"The assembly {assemblyName} was specified multiple times. Hosting startup assemblies should only be specified once.");
+            }
+
+            AddApplicationServices(applicationServices, hostingServiceProvider);
+
+            var host = new WebHost(
+                applicationServices,
+                hostingServiceProvider,
+                _options,
+                _config,
+                hostingStartupErrors);
+            try
+            {
+                host.Initialize();
+
+                return host;
+            }
+            catch
+            {
+                // Dispose the host if there's a failure to initialize, this should clean up
+                // will dispose services that were constructed until the exception was thrown
+                host.Dispose();
+                throw;
+            }
+
+            IServiceProvider GetProviderFromFactory(IServiceCollection collection)
+            {
+                var provider = collection.BuildServiceProvider();
+                var factory = provider.GetService<IServiceProviderFactory<IServiceCollection>>();
+
+                if (factory != null)
+                {
+                    using (provider)
+                    {
+                        return factory.CreateServiceProvider(factory.CreateBuilder(collection));
+                    }
+                }
+
+                return provider;
+            }
+        }
+
+        private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
+        {
+            hostingStartupErrors = null;
+
+            _options = new WebHostOptions(_config, Assembly.GetEntryAssembly()?.GetName().Name);
+
+            if (!_options.PreventHostingStartup)
+            {
+                var exceptions = new List<Exception>();
+
+                // Execute the hosting startup assemblies
+                foreach (var assemblyName in _options.GetFinalHostingStartupAssemblies().Distinct(StringComparer.OrdinalIgnoreCase))
+                {
+                    try
+                    {
+                        var assembly = Assembly.Load(new AssemblyName(assemblyName));
+
+                        foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>())
+                        {
+                            var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType);
+                            hostingStartup.Configure(this);
+                        }
+                    }
+                    catch (Exception ex)
+                    {
+                        // Capture any errors that happen during startup
+                        exceptions.Add(new InvalidOperationException($"Startup assembly {assemblyName} failed to execute. See the inner exception for more details.", ex));
+                    }
+                }
+
+                if (exceptions.Count > 0)
+                {
+                    hostingStartupErrors = new AggregateException(exceptions);
+                }
+            }
+
+            var contentRootPath = ResolveContentRootPath(_options.ContentRootPath, AppContext.BaseDirectory);
+
+            // Initialize the hosting environment
+            _hostingEnvironment.Initialize(contentRootPath, _options);
+            _context.HostingEnvironment = _hostingEnvironment;
+
+            var services = new ServiceCollection();
+            services.AddSingleton(_options);
+            services.AddSingleton<IHostingEnvironment>(_hostingEnvironment);
+            services.AddSingleton<Extensions.Hosting.IHostingEnvironment>(_hostingEnvironment);
+            services.AddSingleton(_context);
+
+            var builder = new ConfigurationBuilder()
+                .SetBasePath(_hostingEnvironment.ContentRootPath)
+                .AddConfiguration(_config);
+
+            foreach (var configureAppConfiguration in _configureAppConfigurationBuilderDelegates)
+            {
+                configureAppConfiguration(_context, builder);
+            }
+
+            var configuration = builder.Build();
+            services.AddSingleton<IConfiguration>(configuration);
+            _context.Configuration = configuration;
+
+            var listener = new DiagnosticListener("Microsoft.AspNetCore");
+            services.AddSingleton<DiagnosticListener>(listener);
+            services.AddSingleton<DiagnosticSource>(listener);
+
+            services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();
+            services.AddTransient<IHttpContextFactory, HttpContextFactory>();
+            services.AddScoped<IMiddlewareFactory, MiddlewareFactory>();
+            services.AddOptions();
+            services.AddLogging();
+
+            // Conjure up a RequestServices
+            services.AddTransient<IStartupFilter, AutoRequestServicesStartupFilter>();
+            services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
+
+            // Ensure object pooling is available everywhere.
+            services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
+
+            if (!string.IsNullOrEmpty(_options.StartupAssembly))
+            {
+                try
+                {
+                    var startupType = StartupLoader.FindStartupType(_options.StartupAssembly, _hostingEnvironment.EnvironmentName);
+
+                    if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
+                    {
+                        services.AddSingleton(typeof(IStartup), startupType);
+                    }
+                    else
+                    {
+                        services.AddSingleton(typeof(IStartup), sp =>
+                        {
+                            var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
+                            var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName);
+                            return new ConventionBasedStartup(methods);
+                        });
+                    }
+                }
+                catch (Exception ex)
+                {
+                    var capture = ExceptionDispatchInfo.Capture(ex);
+                    services.AddSingleton<IStartup>(_ =>
+                    {
+                        capture.Throw();
+                        return null;
+                    });
+                }
+            }
+
+            foreach (var configureServices in _configureServicesDelegates)
+            {
+                configureServices(_context, services);
+            }
+
+            return services;
+        }
+
+        private void AddApplicationServices(IServiceCollection services, IServiceProvider hostingServiceProvider)
+        {
+            // We are forwarding services from hosting container so hosting container
+            // can still manage their lifetime (disposal) shared instances with application services.
+            // NOTE: This code overrides original services lifetime. Instances would always be singleton in
+            // application container.
+            var listener = hostingServiceProvider.GetService<DiagnosticListener>();
+            services.Replace(ServiceDescriptor.Singleton(typeof(DiagnosticListener), listener));
+            services.Replace(ServiceDescriptor.Singleton(typeof(DiagnosticSource), listener));
+        }
+
+        private string ResolveContentRootPath(string contentRootPath, string basePath)
+        {
+            if (string.IsNullOrEmpty(contentRootPath))
+            {
+                return basePath;
+            }
+            if (Path.IsPathRooted(contentRootPath))
+            {
+                return contentRootPath;
+            }
+            return Path.Combine(Path.GetFullPath(basePath), contentRootPath);
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs b/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..09c7e6d96b154bd8b0fcf8ad50117d4e450c68bf
--- /dev/null
+++ b/src/Hosting/Hosting/src/WebHostBuilderExtensions.cs
@@ -0,0 +1,148 @@
+// 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.Reflection;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting.Internal;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    public static class WebHostBuilderExtensions
+    {
+        /// <summary>
+        /// Specify the startup method to be used to configure the web application.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
+        /// <param name="configureApp">The delegate that configures the <see cref="IApplicationBuilder"/>.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder Configure(this IWebHostBuilder hostBuilder, Action<IApplicationBuilder> configureApp)
+        {
+            if (configureApp == null)
+            {
+                throw new ArgumentNullException(nameof(configureApp));
+            }
+
+            var startupAssemblyName = configureApp.GetMethodInfo().DeclaringType.GetTypeInfo().Assembly.GetName().Name;
+
+            return hostBuilder
+                .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
+                .ConfigureServices(services =>
+                {
+                    services.AddSingleton<IStartup>(sp =>
+                    {
+                        return new DelegateStartup(sp.GetRequiredService<IServiceProviderFactory<IServiceCollection>>(), configureApp);
+                    });
+                });
+        }
+
+
+        /// <summary>
+        /// Specify the startup type to be used by the web host.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
+        /// <param name="startupType">The <see cref="Type"/> to be used.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
+        {
+            var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name;
+
+            return hostBuilder
+                .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
+                .ConfigureServices(services =>
+                {
+                    if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
+                    {
+                        services.AddSingleton(typeof(IStartup), startupType);
+                    }
+                    else
+                    {
+                        services.AddSingleton(typeof(IStartup), sp =>
+                        {
+                            var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
+                            return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
+                        });
+                    }
+                });
+        }
+
+        /// <summary>
+        /// Specify the startup type to be used by the web host.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
+        /// <typeparam name ="TStartup">The type containing the startup methods for the application.</typeparam>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder UseStartup<TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class
+        {
+            return hostBuilder.UseStartup(typeof(TStartup));
+        }
+
+        /// <summary>
+        /// Configures the default service provider
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
+        /// <param name="configure">A callback used to configure the <see cref="ServiceProviderOptions"/> for the default <see cref="IServiceProvider"/>.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder UseDefaultServiceProvider(this IWebHostBuilder hostBuilder, Action<ServiceProviderOptions> configure)
+        {
+            return hostBuilder.UseDefaultServiceProvider((context, options) => configure(options));
+        }
+
+        /// <summary>
+        /// Configures the default service provider
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
+        /// <param name="configure">A callback used to configure the <see cref="ServiceProviderOptions"/> for the default <see cref="IServiceProvider"/>.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder UseDefaultServiceProvider(this IWebHostBuilder hostBuilder, Action<WebHostBuilderContext, ServiceProviderOptions> configure)
+        {
+            return hostBuilder.ConfigureServices((context, services) =>
+            {
+                var options = new ServiceProviderOptions();
+                configure(context, options);
+                services.Replace(ServiceDescriptor.Singleton<IServiceProviderFactory<IServiceCollection>>(new DefaultServiceProviderFactory(options)));
+            });
+        }
+
+        /// <summary>
+        /// Adds a delegate for configuring the <see cref="IConfigurationBuilder"/> that will construct an <see cref="IConfiguration"/>.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
+        /// <param name="configureDelegate">The delegate for configuring the <see cref="IConfigurationBuilder" /> that will be used to construct an <see cref="IConfiguration" />.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        /// <remarks>
+        /// The <see cref="IConfiguration"/> and <see cref="ILoggerFactory"/> on the <see cref="WebHostBuilderContext"/> are uninitialized at this stage.
+        /// The <see cref="IConfigurationBuilder"/> is pre-populated with the settings of the <see cref="IWebHostBuilder"/>.
+        /// </remarks>
+        public static IWebHostBuilder ConfigureAppConfiguration(this IWebHostBuilder hostBuilder, Action<IConfigurationBuilder> configureDelegate)
+        {
+            return hostBuilder.ConfigureAppConfiguration((context, builder) => configureDelegate(builder));
+        }
+
+        /// <summary>
+        /// Adds a delegate for configuring the provided <see cref="ILoggingBuilder"/>. This may be called multiple times.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder" /> to configure.</param>
+        /// <param name="configureLogging">The delegate that configures the <see cref="ILoggingBuilder"/>.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder ConfigureLogging(this IWebHostBuilder hostBuilder, Action<ILoggingBuilder> configureLogging)
+        {
+            return hostBuilder.ConfigureServices(collection => collection.AddLogging(configureLogging));
+        }
+
+        /// <summary>
+        /// Adds a delegate for configuring the provided <see cref="LoggerFactory"/>. This may be called multiple times.
+        /// </summary>
+        /// <param name="hostBuilder">The <see cref="IWebHostBuilder" /> to configure.</param>
+        /// <param name="configureLogging">The delegate that configures the <see cref="LoggerFactory"/>.</param>
+        /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
+        public static IWebHostBuilder ConfigureLogging(this IWebHostBuilder hostBuilder, Action<WebHostBuilderContext, ILoggingBuilder> configureLogging)
+        {
+            return hostBuilder.ConfigureServices((context, collection) => collection.AddLogging(builder => configureLogging(context, builder)));
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/src/WebHostExtensions.cs b/src/Hosting/Hosting/src/WebHostExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..06a3e00cf887b6c4f44e452b9b6074452945a176
--- /dev/null
+++ b/src/Hosting/Hosting/src/WebHostExtensions.cs
@@ -0,0 +1,176 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.Hosting.Internal;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    public static class WebHostExtensions
+    {
+        /// <summary>
+        /// Attempts to gracefully stop the host with the given timeout.
+        /// </summary>
+        /// <param name="host"></param>
+        /// <param name="timeout">The timeout for stopping gracefully. Once expired the
+        /// server may terminate any remaining active connections.</param>
+        /// <returns></returns>
+        public static Task StopAsync(this IWebHost host, TimeSpan timeout)
+        {
+            return host.StopAsync(new CancellationTokenSource(timeout).Token);
+        }
+
+        /// <summary>
+        /// Block the calling thread until shutdown is triggered via Ctrl+C or SIGTERM.
+        /// </summary>
+        /// <param name="host">The running <see cref="IWebHost"/>.</param>
+        public static void WaitForShutdown(this IWebHost host)
+        {
+            host.WaitForShutdownAsync().GetAwaiter().GetResult();
+        }
+
+        /// <summary>
+        /// Returns a Task that completes when shutdown is triggered via the given token, Ctrl+C or SIGTERM.
+        /// </summary>
+        /// <param name="host">The running <see cref="IWebHost"/>.</param>
+        /// <param name="token">The token to trigger shutdown.</param>
+        public static async Task WaitForShutdownAsync(this IWebHost host, CancellationToken token = default)
+        {
+            var done = new ManualResetEventSlim(false);
+            using (var cts = CancellationTokenSource.CreateLinkedTokenSource(token))
+            {
+                AttachCtrlcSigtermShutdown(cts, done, shutdownMessage: string.Empty);
+
+                await host.WaitForTokenShutdownAsync(cts.Token);
+                done.Set();
+            }
+        }
+
+        /// <summary>
+        /// Runs a web application and block the calling thread until host shutdown.
+        /// </summary>
+        /// <param name="host">The <see cref="IWebHost"/> to run.</param>
+        public static void Run(this IWebHost host)
+        {
+            host.RunAsync().GetAwaiter().GetResult();
+        }
+
+        /// <summary>
+        /// Runs a web application and returns a Task that only completes when the token is triggered or shutdown is triggered.
+        /// </summary>
+        /// <param name="host">The <see cref="IWebHost"/> to run.</param>
+        /// <param name="token">The token to trigger shutdown.</param>
+        public static async Task RunAsync(this IWebHost host, CancellationToken token = default)
+        {
+            // Wait for token shutdown if it can be canceled
+            if (token.CanBeCanceled)
+            {
+                await host.RunAsync(token, shutdownMessage: null);
+                return;
+            }
+
+            // If token cannot be canceled, attach Ctrl+C and SIGTERM shutdown
+            var done = new ManualResetEventSlim(false);
+            using (var cts = new CancellationTokenSource())
+            {
+                var shutdownMessage = host.Services.GetRequiredService<WebHostOptions>().SuppressStatusMessages ? string.Empty : "Application is shutting down...";
+                AttachCtrlcSigtermShutdown(cts, done, shutdownMessage: shutdownMessage);
+
+                await host.RunAsync(cts.Token, "Application started. Press Ctrl+C to shut down.");
+                done.Set();
+            }
+        }
+
+        private static async Task RunAsync(this IWebHost host, CancellationToken token, string shutdownMessage)
+        {
+            using (host)
+            {
+                await host.StartAsync(token);
+
+                var hostingEnvironment = host.Services.GetService<IHostingEnvironment>();
+                var applicationLifetime = host.Services.GetService<IApplicationLifetime>();
+                var options = host.Services.GetRequiredService<WebHostOptions>();
+
+                if (!options.SuppressStatusMessages)
+                {
+                    Console.WriteLine($"Hosting environment: {hostingEnvironment.EnvironmentName}");
+                    Console.WriteLine($"Content root path: {hostingEnvironment.ContentRootPath}");
+
+
+                    var serverAddresses = host.ServerFeatures.Get<IServerAddressesFeature>()?.Addresses;
+                    if (serverAddresses != null)
+                    {
+                        foreach (var address in serverAddresses)
+                        {
+                            Console.WriteLine($"Now listening on: {address}");
+                        }
+                    }
+
+                    if (!string.IsNullOrEmpty(shutdownMessage))
+                    {
+                        Console.WriteLine(shutdownMessage);
+                    }
+                }
+
+                await host.WaitForTokenShutdownAsync(token);
+            }
+        }
+
+        private static void AttachCtrlcSigtermShutdown(CancellationTokenSource cts, ManualResetEventSlim resetEvent, string shutdownMessage)
+        {
+            void Shutdown()
+            {
+                if (!cts.IsCancellationRequested)
+                {
+                    if (!string.IsNullOrEmpty(shutdownMessage))
+                    {
+                        Console.WriteLine(shutdownMessage);
+                    }
+                    try
+                    {
+                        cts.Cancel();
+                    }
+                    catch (ObjectDisposedException) { }
+                }
+
+                // Wait on the given reset event
+                resetEvent.Wait();
+            };
+
+            AppDomain.CurrentDomain.ProcessExit += (sender, eventArgs) => Shutdown();
+            Console.CancelKeyPress += (sender, eventArgs) =>
+            {
+                Shutdown();
+                // Don't terminate the process immediately, wait for the Main thread to exit gracefully.
+                eventArgs.Cancel = true;
+            };
+        }
+
+        private static async Task WaitForTokenShutdownAsync(this IWebHost host, CancellationToken token)
+        {
+            var applicationLifetime = host.Services.GetService<IApplicationLifetime>();
+
+            token.Register(state =>
+            {
+                ((IApplicationLifetime)state).StopApplication();
+            },
+            applicationLifetime);
+
+            var waitForStop = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
+            applicationLifetime.ApplicationStopping.Register(obj =>
+            {
+                var tcs = (TaskCompletionSource<object>)obj;
+                tcs.TrySetResult(null);
+            }, waitForStop);
+
+            await waitForStop.Task;
+
+            // WebHost will use its default ShutdownTimeout if none is specified.
+            await host.StopAsync();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/baseline.netcore.json b/src/Hosting/Hosting/src/baseline.netcore.json
new file mode 100644
index 0000000000000000000000000000000000000000..ca859909149139c922bb272e8f0b55e011527d22
--- /dev/null
+++ b/src/Hosting/Hosting/src/baseline.netcore.json
@@ -0,0 +1,1995 @@
+{
+  "AssemblyIdentity": "Microsoft.AspNetCore.Hosting, Version=2.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+  "Types": [
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.ConventionBasedStartup",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Hosting.IStartup"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Configure",
+          "Parameters": [
+            {
+              "Name": "app",
+              "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IStartup",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ConfigureServices",
+          "Parameters": [
+            {
+              "Name": "services",
+              "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
+            }
+          ],
+          "ReturnType": "System.IServiceProvider",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IStartup",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "methods",
+              "Type": "Microsoft.AspNetCore.Hosting.Internal.StartupMethods"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.DelegateStartup",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "BaseType": "Microsoft.AspNetCore.Hosting.StartupBase<Microsoft.Extensions.DependencyInjection.IServiceCollection>",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Configure",
+          "Parameters": [
+            {
+              "Name": "app",
+              "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IStartup",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "factory",
+              "Type": "Microsoft.Extensions.DependencyInjection.IServiceProviderFactory<Microsoft.Extensions.DependencyInjection.IServiceCollection>"
+            },
+            {
+              "Name": "configureApp",
+              "Type": "System.Action<Microsoft.AspNetCore.Builder.IApplicationBuilder>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.StartupBase",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Hosting.IStartup"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Configure",
+          "Parameters": [
+            {
+              "Name": "app",
+              "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IStartup",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ConfigureServices",
+          "Parameters": [
+            {
+              "Name": "services",
+              "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "CreateServiceProvider",
+          "Parameters": [
+            {
+              "Name": "services",
+              "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
+            }
+          ],
+          "ReturnType": "System.IServiceProvider",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Protected",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.StartupBase<T0>",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "BaseType": "Microsoft.AspNetCore.Hosting.StartupBase",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "CreateServiceProvider",
+          "Parameters": [
+            {
+              "Name": "services",
+              "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
+            }
+          ],
+          "ReturnType": "System.IServiceProvider",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ConfigureContainer",
+          "Parameters": [
+            {
+              "Name": "builder",
+              "Type": "T0"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "factory",
+              "Type": "Microsoft.Extensions.DependencyInjection.IServiceProviderFactory<T0>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": [
+        {
+          "ParameterName": "TBuilder",
+          "ParameterPosition": 0,
+          "BaseTypeOrInterfaces": []
+        }
+      ]
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.WebHostBuilder",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "GetSetting",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseSetting",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ConfigureServices",
+          "Parameters": [
+            {
+              "Name": "configureServices",
+              "Type": "System.Action<Microsoft.Extensions.DependencyInjection.IServiceCollection>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ConfigureServices",
+          "Parameters": [
+            {
+              "Name": "configureServices",
+              "Type": "System.Action<Microsoft.AspNetCore.Hosting.WebHostBuilderContext, Microsoft.Extensions.DependencyInjection.IServiceCollection>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ConfigureAppConfiguration",
+          "Parameters": [
+            {
+              "Name": "configureDelegate",
+              "Type": "System.Action<Microsoft.AspNetCore.Hosting.WebHostBuilderContext, Microsoft.Extensions.Configuration.IConfigurationBuilder>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Build",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHost",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.WebHostBuilderExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Configure",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "configureApp",
+              "Type": "System.Action<Microsoft.AspNetCore.Builder.IApplicationBuilder>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseStartup",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "startupType",
+              "Type": "System.Type"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseStartup<T0>",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "TStartup",
+              "ParameterPosition": 0,
+              "Class": true,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseDefaultServiceProvider",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "configure",
+              "Type": "System.Action<Microsoft.Extensions.DependencyInjection.ServiceProviderOptions>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseDefaultServiceProvider",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "configure",
+              "Type": "System.Action<Microsoft.AspNetCore.Hosting.WebHostBuilderContext, Microsoft.Extensions.DependencyInjection.ServiceProviderOptions>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ConfigureAppConfiguration",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "configureDelegate",
+              "Type": "System.Action<Microsoft.Extensions.Configuration.IConfigurationBuilder>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ConfigureLogging",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "configureLogging",
+              "Type": "System.Action<Microsoft.Extensions.Logging.ILoggingBuilder>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ConfigureLogging",
+          "Parameters": [
+            {
+              "Name": "hostBuilder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "configureLogging",
+              "Type": "System.Action<Microsoft.AspNetCore.Hosting.WebHostBuilderContext, Microsoft.Extensions.Logging.ILoggingBuilder>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.WebHostExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "StopAsync",
+          "Parameters": [
+            {
+              "Name": "host",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHost"
+            },
+            {
+              "Name": "timeout",
+              "Type": "System.TimeSpan"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "WaitForShutdown",
+          "Parameters": [
+            {
+              "Name": "host",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHost"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "WaitForShutdownAsync",
+          "Parameters": [
+            {
+              "Name": "host",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHost"
+            },
+            {
+              "Name": "token",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Run",
+          "Parameters": [
+            {
+              "Name": "host",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHost"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "RunAsync",
+          "Parameters": [
+            {
+              "Name": "host",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHost"
+            },
+            {
+              "Name": "token",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Server.Features.ServerAddressesFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Addresses",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.ICollection<System.String>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_PreferHostingUrls",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_PreferHostingUrls",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Internal.ApplicationLifetime",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Hosting.IApplicationLifetime"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_ApplicationStarted",
+          "Parameters": [],
+          "ReturnType": "System.Threading.CancellationToken",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IApplicationLifetime",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ApplicationStopping",
+          "Parameters": [],
+          "ReturnType": "System.Threading.CancellationToken",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IApplicationLifetime",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ApplicationStopped",
+          "Parameters": [],
+          "ReturnType": "System.Threading.CancellationToken",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IApplicationLifetime",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "StopApplication",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IApplicationLifetime",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "NotifyStarted",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "NotifyStopped",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "logger",
+              "Type": "Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Hosting.Internal.ApplicationLifetime>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Internal.AutoRequestServicesStartupFilter",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Hosting.IStartupFilter"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Configure",
+          "Parameters": [
+            {
+              "Name": "next",
+              "Type": "System.Action<Microsoft.AspNetCore.Builder.IApplicationBuilder>"
+            }
+          ],
+          "ReturnType": "System.Action<Microsoft.AspNetCore.Builder.IApplicationBuilder>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IStartupFilter",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Internal.ConfigureBuilder",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_MethodInfo",
+          "Parameters": [],
+          "ReturnType": "System.Reflection.MethodInfo",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Build",
+          "Parameters": [
+            {
+              "Name": "instance",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Action<Microsoft.AspNetCore.Builder.IApplicationBuilder>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "configure",
+              "Type": "System.Reflection.MethodInfo"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Internal.ConfigureContainerBuilder",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_MethodInfo",
+          "Parameters": [],
+          "ReturnType": "System.Reflection.MethodInfo",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Build",
+          "Parameters": [
+            {
+              "Name": "instance",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Action<System.Object>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetContainerType",
+          "Parameters": [],
+          "ReturnType": "System.Type",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "configureContainerMethod",
+              "Type": "System.Reflection.MethodInfo"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Internal.ConfigureServicesBuilder",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_MethodInfo",
+          "Parameters": [],
+          "ReturnType": "System.Reflection.MethodInfo",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Build",
+          "Parameters": [
+            {
+              "Name": "instance",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Func<Microsoft.Extensions.DependencyInjection.IServiceCollection, System.IServiceProvider>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "configureServices",
+              "Type": "System.Reflection.MethodInfo"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "StartAsync",
+          "Parameters": [
+            {
+              "Name": "token",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "StopAsync",
+          "Parameters": [
+            {
+              "Name": "token",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "logger",
+              "Type": "Microsoft.Extensions.Logging.ILogger<Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor>"
+            },
+            {
+              "Name": "services",
+              "Type": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Hosting.IHostedService>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Internal.HostingApplication",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Hosting.Server.IHttpApplication<Microsoft.AspNetCore.Hosting.Internal.HostingApplication+Context>"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "CreateContext",
+          "Parameters": [
+            {
+              "Name": "contextFeatures",
+              "Type": "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.Internal.HostingApplication+Context",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.Server.IHttpApplication<Microsoft.AspNetCore.Hosting.Internal.HostingApplication+Context>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ProcessRequestAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Hosting.Internal.HostingApplication+Context"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.Server.IHttpApplication<Microsoft.AspNetCore.Hosting.Internal.HostingApplication+Context>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "DisposeContext",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Hosting.Internal.HostingApplication+Context"
+            },
+            {
+              "Name": "exception",
+              "Type": "System.Exception"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.Server.IHttpApplication<Microsoft.AspNetCore.Hosting.Internal.HostingApplication+Context>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "application",
+              "Type": "Microsoft.AspNetCore.Http.RequestDelegate"
+            },
+            {
+              "Name": "logger",
+              "Type": "Microsoft.Extensions.Logging.ILogger"
+            },
+            {
+              "Name": "diagnosticSource",
+              "Type": "System.Diagnostics.DiagnosticListener"
+            },
+            {
+              "Name": "httpContextFactory",
+              "Type": "Microsoft.AspNetCore.Http.IHttpContextFactory"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Internal.HostingEnvironment",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Hosting.IHostingEnvironment"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_EnvironmentName",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IHostingEnvironment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_EnvironmentName",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IHostingEnvironment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ApplicationName",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IHostingEnvironment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ApplicationName",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IHostingEnvironment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_WebRootPath",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IHostingEnvironment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_WebRootPath",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IHostingEnvironment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_WebRootFileProvider",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.FileProviders.IFileProvider",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IHostingEnvironment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_WebRootFileProvider",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.FileProviders.IFileProvider"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IHostingEnvironment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentRootPath",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IHostingEnvironment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentRootPath",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IHostingEnvironment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentRootFileProvider",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.FileProviders.IFileProvider",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IHostingEnvironment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentRootFileProvider",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.FileProviders.IFileProvider"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.IHostingEnvironment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Internal.HostingEnvironmentExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Initialize",
+          "Parameters": [
+            {
+              "Name": "hostingEnvironment",
+              "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment"
+            },
+            {
+              "Name": "applicationName",
+              "Type": "System.String"
+            },
+            {
+              "Name": "contentRootPath",
+              "Type": "System.String"
+            },
+            {
+              "Name": "options",
+              "Type": "Microsoft.AspNetCore.Hosting.Internal.WebHostOptions"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Internal.HostingEventSource",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Sealed": true,
+      "BaseType": "System.Diagnostics.Tracing.EventSource",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "HostStart",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "HostStop",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "RequestStart",
+          "Parameters": [
+            {
+              "Name": "method",
+              "Type": "System.String"
+            },
+            {
+              "Name": "path",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "RequestStop",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UnhandledException",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Log",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.Internal.HostingEventSource",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Internal.RequestServicesContainerMiddleware",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Invoke",
+          "Parameters": [
+            {
+              "Name": "httpContext",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "next",
+              "Type": "Microsoft.AspNetCore.Http.RequestDelegate"
+            },
+            {
+              "Name": "scopeFactory",
+              "Type": "Microsoft.Extensions.DependencyInjection.IServiceScopeFactory"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Internal.RequestServicesFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.Features.IServiceProvidersFeature",
+        "System.IDisposable"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_RequestServices",
+          "Parameters": [],
+          "ReturnType": "System.IServiceProvider",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IServiceProvidersFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RequestServices",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.IServiceProvider"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IServiceProvidersFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Dispose",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.IDisposable",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "scopeFactory",
+              "Type": "Microsoft.Extensions.DependencyInjection.IServiceScopeFactory"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Internal.StartupLoader",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "LoadMethods",
+          "Parameters": [
+            {
+              "Name": "hostingServiceProvider",
+              "Type": "System.IServiceProvider"
+            },
+            {
+              "Name": "startupType",
+              "Type": "System.Type"
+            },
+            {
+              "Name": "environmentName",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.Internal.StartupMethods",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "FindStartupType",
+          "Parameters": [
+            {
+              "Name": "startupAssemblyName",
+              "Type": "System.String"
+            },
+            {
+              "Name": "environmentName",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Type",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Internal.StartupMethods",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_StartupInstance",
+          "Parameters": [],
+          "ReturnType": "System.Object",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ConfigureServicesDelegate",
+          "Parameters": [],
+          "ReturnType": "System.Func<Microsoft.Extensions.DependencyInjection.IServiceCollection, System.IServiceProvider>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ConfigureDelegate",
+          "Parameters": [],
+          "ReturnType": "System.Action<Microsoft.AspNetCore.Builder.IApplicationBuilder>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "instance",
+              "Type": "System.Object"
+            },
+            {
+              "Name": "configure",
+              "Type": "System.Action<Microsoft.AspNetCore.Builder.IApplicationBuilder>"
+            },
+            {
+              "Name": "configureServices",
+              "Type": "System.Func<Microsoft.Extensions.DependencyInjection.IServiceCollection, System.IServiceProvider>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Internal.WebHostOptions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_ApplicationName",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ApplicationName",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_PreventHostingStartup",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_PreventHostingStartup",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HostingStartupAssemblies",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IReadOnlyList<System.String>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_HostingStartupAssemblies",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Collections.Generic.IReadOnlyList<System.String>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_DetailedErrors",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_DetailedErrors",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_CaptureStartupErrors",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_CaptureStartupErrors",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Environment",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Environment",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_StartupAssembly",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_StartupAssembly",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_WebRoot",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_WebRoot",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentRootPath",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentRootPath",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ShutdownTimeout",
+          "Parameters": [],
+          "ReturnType": "System.TimeSpan",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ShutdownTimeout",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.TimeSpan"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "configuration",
+              "Type": "Microsoft.Extensions.Configuration.IConfiguration"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Internal.WebHostUtilities",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "ParseBool",
+          "Parameters": [
+            {
+              "Name": "configuration",
+              "Type": "Microsoft.Extensions.Configuration.IConfiguration"
+            },
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Builder.ApplicationBuilderFactory",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "CreateBuilder",
+          "Parameters": [
+            {
+              "Name": "serverFeatures",
+              "Type": "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "serviceProvider",
+              "Type": "System.IServiceProvider"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Builder.IApplicationBuilderFactory",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "CreateBuilder",
+          "Parameters": [
+            {
+              "Name": "serverFeatures",
+              "Type": "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Internal.HostingApplication+Context",
+      "Visibility": "Public",
+      "Kind": "Struct",
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_HttpContext",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.HttpContext",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_HttpContext",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Scope",
+          "Parameters": [],
+          "ReturnType": "System.IDisposable",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Scope",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.IDisposable"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_StartTimestamp",
+          "Parameters": [],
+          "ReturnType": "System.Int64",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_StartTimestamp",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int64"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_EventLogEnabled",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_EventLogEnabled",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Activity",
+          "Parameters": [],
+          "ReturnType": "System.Diagnostics.Activity",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Activity",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Diagnostics.Activity"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/src/compiler/resources/GenericError.html b/src/Hosting/Hosting/src/compiler/resources/GenericError.html
new file mode 100644
index 0000000000000000000000000000000000000000..c6b24c57e852e2294387e2da2429b948c6c8d48b
--- /dev/null
+++ b/src/Hosting/Hosting/src/compiler/resources/GenericError.html
@@ -0,0 +1,146 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+    <meta charset="utf-8" />
+    <title>500 Internal Server Error</title>
+    <style type="text/css">
+        body {
+            background-color: white;
+            color: #111111;
+            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+            margin: 2em 4em;
+        }
+
+        footer a {
+            color: darkblue;
+            text-decoration: none;
+            font-weight: bolder;
+        }
+
+        #header {
+            margin-bottom: 2.5em;
+        }
+
+        .stacktrace pre {
+            display: inline;
+        }
+
+        .faded {
+            color: #999999;
+            font-weight: normal;
+        }
+
+        div.message {
+            margin-top: 2.5em;
+            padding: 0.3em 1em;
+            border-left: 0.25em solid red;
+        }
+
+        .light {
+            font-size: 1.3em;
+            font-weight: lighter;
+        }
+
+        .heavy {
+            font-size: 1.5em;
+        }
+
+        .exception {
+            color: red;
+        }
+
+        .stacktrace {
+            padding-top: 0.3em;
+            padding-left: 2em;
+            display: block;
+            font-weight: bold;
+        }
+
+        .codeSnippet {
+            margin-left: 2em;
+            margin-top: 1em;
+            margin-bottom: 1em;
+            display: inline-block;
+            border-top: 0.2em solid #cccccc;
+            border-bottom: 0.2em solid #cccccc;
+            color: black;
+        }
+
+        .codeSnippet div:nth-of-type(2n) {
+            background-color: #f0f0f0;
+        }
+
+        .codeSnippet div:nth-of-type(2n + 1) {
+            background-color: #f6f6f6;
+        }
+
+        .codeSnippet div.filename {
+            font-weight: bold;
+            background-color: white;
+            margin: 0.6em;
+        }
+
+        .codeSnippet div.line {
+            padding: 0.2em;
+            line-height: 1em;
+        }
+
+        .codeSnippet div.line .line-number {
+            color: #999999;
+            text-align: right;
+            margin-right: 0.5em;
+        }
+
+        .codeSnippet div.error {
+            color: red;
+            font-weight: bolder;
+            background-color: #ffeda7;
+        }
+
+        .codeSnippet code {
+            white-space: pre;
+        }
+
+        .rawExceptionBlock {
+            margin-top: 1em;
+            margin-left: 1em;
+        }
+
+        #rawException {
+            display: none;
+        }
+
+        footer {
+            margin-top: 2em;
+            font-size: smaller;
+            font-weight: lighter;
+        }
+    </style>
+    <script type="text/javascript">
+        function showRawException() {
+            var div = document.getElementById('rawException');
+            div.style.display = 'inline-block';
+            div.scrollIntoView(true);
+        }
+    </script>
+</head>
+<body>
+
+    <div id="header">
+        <div style="font-size: 6em; display: inline-block;">
+            :(
+        </div>
+        <div style="display: inline-block; padding-left: 3em;">
+            <span style="font-size: 2em;">Oops.</span><br />
+            <span style="font-size: 1.65em; font-weight: lighter;">500 Internal Server Error</span>
+        </div>
+    </div>
+
+    [[[0]]]
+
+    [[[1]]]
+
+    [[[2]]]
+</body>
+</html>
diff --git a/src/Hosting/Hosting/src/compiler/resources/GenericError_Exception.html b/src/Hosting/Hosting/src/compiler/resources/GenericError_Exception.html
new file mode 100644
index 0000000000000000000000000000000000000000..012e05bee7dfdf4f32ad1371d80df985f522e559
--- /dev/null
+++ b/src/Hosting/Hosting/src/compiler/resources/GenericError_Exception.html
@@ -0,0 +1,8 @@
+<div class="message">
+    <span class="light exception">{0}</span><br />
+    <span class="heavy">{1}</span><br />
+    {2}
+    <div class="stacktrace">
+        {3}
+    </div>
+</div>
diff --git a/src/Hosting/Hosting/src/compiler/resources/GenericError_Footer.html b/src/Hosting/Hosting/src/compiler/resources/GenericError_Footer.html
new file mode 100644
index 0000000000000000000000000000000000000000..fe54861d87771f8078ab264b1f9ebd4e0b517c35
--- /dev/null
+++ b/src/Hosting/Hosting/src/compiler/resources/GenericError_Footer.html
@@ -0,0 +1,3 @@
+<footer>
+    {0} {1} v{2}&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;Microsoft.AspNetCore.Hosting version {3}&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;{4}&nbsp;&nbsp;&nbsp;|&nbsp;&nbsp;&nbsp;<a href="http://go.microsoft.com/fwlink/?LinkId=517394">Need help?</a>
+</footer>
diff --git a/src/Hosting/Hosting/src/compiler/resources/GenericError_Message.html b/src/Hosting/Hosting/src/compiler/resources/GenericError_Message.html
new file mode 100644
index 0000000000000000000000000000000000000000..39a83d8754cdfdace8d6e046e371053b62eec115
--- /dev/null
+++ b/src/Hosting/Hosting/src/compiler/resources/GenericError_Message.html
@@ -0,0 +1,3 @@
+<div class="message">
+    <span class="heavy">{0}</span><br />
+</div>
diff --git a/src/Hosting/Hosting/test/ConfigureBuilderTests.cs b/src/Hosting/Hosting/test/ConfigureBuilderTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..cea4235c8c7edef0c8ec6fe0227c5988175a38b2
--- /dev/null
+++ b/src/Hosting/Hosting/test/ConfigureBuilderTests.cs
@@ -0,0 +1,55 @@
+// 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.Reflection;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Builder.Internal;
+using Microsoft.AspNetCore.Hosting.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Hosting.Tests
+{
+    public class ConfigureBuilderTests
+    {
+        [Fact]
+        public void CapturesServiceExceptionDetails()
+        {
+            var methodInfo = GetType().GetMethod(nameof(InjectedMethod), BindingFlags.NonPublic | BindingFlags.Static);
+            Assert.NotNull(methodInfo);
+
+            var services = new ServiceCollection()
+                .AddSingleton<CrasherService>()
+                .BuildServiceProvider();
+
+            var applicationBuilder = new ApplicationBuilder(services);
+
+            var builder = new ConfigureBuilder(methodInfo);
+            Action<IApplicationBuilder> action = builder.Build(instance:null);
+            var ex = Assert.Throws<Exception>(() => action.Invoke(applicationBuilder));
+
+            Assert.NotNull(ex);
+            Assert.Equal($"Could not resolve a service of type '{typeof(CrasherService).FullName}' for the parameter"
+                + $" 'service' of method '{methodInfo.Name}' on type '{methodInfo.DeclaringType.FullName}'.", ex.Message);
+
+            // the inner exception contains the root cause
+            Assert.NotNull(ex.InnerException);
+            Assert.Equal("Service instantiation failed", ex.InnerException.Message);
+            Assert.Contains(nameof(CrasherService), ex.InnerException.StackTrace);
+        }
+
+        private static void InjectedMethod(CrasherService service)
+        {
+            Assert.NotNull(service);
+        }
+
+        private class CrasherService
+        {
+            public CrasherService()
+            {
+                throw new Exception("Service instantiation failed");
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/CustomLoggerFactory.cs b/src/Hosting/Hosting/test/Fakes/CustomLoggerFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9fa7cf2151ee80e279195afd9eb0a2d9b532935a
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/CustomLoggerFactory.cs
@@ -0,0 +1,33 @@
+// 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.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class CustomLoggerFactory : ILoggerFactory
+    {
+        public void CustomConfigureMethod() { }
+
+        public void AddProvider(ILoggerProvider provider) { }
+
+        public ILogger CreateLogger(string categoryName) => NullLogger.Instance;
+
+        public void Dispose() { }
+    }
+
+    public class SubLoggerFactory : CustomLoggerFactory { }
+
+    public class NonSubLoggerFactory : ILoggerFactory
+    {
+        public void CustomConfigureMethod() { }
+
+        public void AddProvider(ILoggerProvider provider) { }
+
+        public ILogger CreateLogger(string categoryName) => NullLogger.Instance;
+
+        public void Dispose() { }
+    }
+}
diff --git a/src/Hosting/Hosting/test/Fakes/FakeOptions.cs b/src/Hosting/Hosting/test/Fakes/FakeOptions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c4ffaa799d80f9f3c55ddc833bc4c1d825c3058f
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/FakeOptions.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.Hosting.Fakes
+{
+    public class FakeOptions
+    {
+        public bool Configured { get; set; }
+        public string Environment { get; set; }
+        public string Message { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/FakeService.cs b/src/Hosting/Hosting/test/Fakes/FakeService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3bf3e6ce38ba657b362e6d85f88f7d43be7f4406
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/FakeService.cs
@@ -0,0 +1,17 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class FakeService : IFakeEveryService, IDisposable
+    {
+        public bool Disposed { get; private set; }
+
+        public void Dispose()
+        {
+            Disposed = true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/IFactoryService.cs b/src/Hosting/Hosting/test/Fakes/IFactoryService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5b78e046e16e10196ce3fce39234541c0e9143ff
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/IFactoryService.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.Hosting.Fakes
+{
+    public interface IFactoryService
+    {
+        IFakeService FakeService { get; }
+
+        int Value { get; }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/IFakeEveryService.cs b/src/Hosting/Hosting/test/Fakes/IFakeEveryService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2cc7a00701be90bf50aa494212cec07874a276c2
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/IFakeEveryService.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.Hosting.Fakes
+{
+    interface IFakeEveryService :
+            IFakeScopedService,
+            IFakeServiceInstance,
+            IFakeSingletonService
+    {
+    }
+}
diff --git a/src/Hosting/Hosting/test/Fakes/IFakeScopedService.cs b/src/Hosting/Hosting/test/Fakes/IFakeScopedService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..77c53e596b401b197b42a3c6f09f56a55050dd1a
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/IFakeScopedService.cs
@@ -0,0 +1,9 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public interface IFakeScopedService : IFakeService
+    {
+    }
+}
diff --git a/src/Hosting/Hosting/test/Fakes/IFakeService.cs b/src/Hosting/Hosting/test/Fakes/IFakeService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..73fca3bab1bf2752113b6b74d88eb7a5e4706983
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/IFakeService.cs
@@ -0,0 +1,7 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public interface IFakeService { }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/IFakeServiceInstance.cs b/src/Hosting/Hosting/test/Fakes/IFakeServiceInstance.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0225a6789fd73eec8742e52056d6b064fddb3212
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/IFakeServiceInstance.cs
@@ -0,0 +1,9 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    interface IFakeServiceInstance : IFakeService
+    {
+    }
+}
diff --git a/src/Hosting/Hosting/test/Fakes/IFakeSingletonService.cs b/src/Hosting/Hosting/test/Fakes/IFakeSingletonService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..93873059994617ede184ed8282f8c2c017c457ec
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/IFakeSingletonService.cs
@@ -0,0 +1,9 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    interface IFakeSingletonService : IFakeService
+    {
+    }
+}
diff --git a/src/Hosting/Hosting/test/Fakes/IFakeStartupCallback.cs b/src/Hosting/Hosting/test/Fakes/IFakeStartupCallback.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8e345a1020d71cda8cdb33e3c5b948ff93d54848
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/IFakeStartupCallback.cs
@@ -0,0 +1,10 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public interface IFakeStartupCallback
+    {
+        void ConfigurationMethodCalled(object instance);
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/INonexistentService.cs b/src/Hosting/Hosting/test/Fakes/INonexistentService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5090051fd6ecac825db36a06fc78471c3d0db6c9
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/INonexistentService.cs
@@ -0,0 +1,9 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public interface INonexistentService
+    {
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/Startup.cs b/src/Hosting/Hosting/test/Fakes/Startup.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2abe4a4b223d2e4402035049eb66cb5a64476e68
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/Startup.cs
@@ -0,0 +1,99 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class Startup : StartupBase
+    {
+        public Startup()
+        {
+        }
+
+        public void ConfigureServices(IServiceCollection services)
+        {
+            services.AddOptions();
+            services.Configure<FakeOptions>(o => o.Configured = true);
+        }
+
+        public void ConfigureDevServices(IServiceCollection services)
+        {
+            services.AddOptions();
+            services.Configure<FakeOptions>(o =>
+            {
+                o.Configured = true;
+                o.Environment = "Dev";
+            });
+        }
+
+        public void ConfigureRetailServices(IServiceCollection services)
+        {
+            services.AddOptions();
+            services.Configure<FakeOptions>(o =>
+            {
+                o.Configured = true;
+                o.Environment = "Retail";
+            });
+        }
+
+        public static void ConfigureStaticServices(IServiceCollection services)
+        {
+            services.AddOptions();
+            services.Configure<FakeOptions>(o =>
+            {
+                o.Configured = true;
+                o.Environment = "Static";
+            });
+        }
+
+        public static IServiceProvider ConfigureStaticProviderServices()
+        {
+            var services = new ServiceCollection().AddOptions();
+            services.Configure<FakeOptions>(o =>
+            {
+                o.Configured = true;
+                o.Environment = "StaticProvider";
+            });
+            return services.BuildServiceProvider();
+        }
+
+        public static IServiceProvider ConfigureFallbackProviderServices(IServiceProvider fallback)
+        {
+            return fallback;
+        }
+
+        public static IServiceProvider ConfigureNullServices()
+        {
+            return null;
+        }
+
+        public IServiceProvider ConfigureProviderServices(IServiceCollection services)
+        {
+            services.AddOptions();
+            services.Configure<FakeOptions>(o =>
+            {
+                o.Configured = true;
+                o.Environment = "Provider";
+            });
+            return services.BuildServiceProvider();
+        }
+
+        public IServiceProvider ConfigureProviderArgsServices()
+        {
+            var services = new ServiceCollection().AddOptions();
+            services.Configure<FakeOptions>(o =>
+            {
+                o.Configured = true;
+                o.Environment = "ProviderArgs";
+            });
+            return services.BuildServiceProvider();
+        }
+
+        public virtual void Configure(IApplicationBuilder builder)
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/StartupBase.cs b/src/Hosting/Hosting/test/Fakes/StartupBase.cs
new file mode 100644
index 0000000000000000000000000000000000000000..82dd2c7cb6e2e080ac57479b5d5c44eca47b5096
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupBase.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 Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class StartupBase
+    {
+        public void ConfigureBaseClassServices(IServiceCollection services)
+        {
+            services.AddOptions();
+            services.Configure<FakeOptions>(o =>
+            {
+                o.Configured = true;
+                o.Environment = "BaseClass";
+            });
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/StartupBoom.cs b/src/Hosting/Hosting/test/Fakes/StartupBoom.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2b629896bc54ee76efde3c8817ab69bda77d1b66
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupBoom.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.Hosting.Fakes
+{
+    public class StartupBoom
+    {
+        public StartupBoom()
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/StartupCaseInsensitive.cs b/src/Hosting/Hosting/test/Fakes/StartupCaseInsensitive.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0c85ad54133c7b2302e9e85b7905f0f7ad0e9690
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupCaseInsensitive.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting.Fakes;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Tests.Fakes
+{
+    class StartupCaseInsensitive
+    {
+        public static IServiceProvider ConfigureCaseInsensitiveServices(IServiceCollection services)
+        {
+            services.AddOptions();
+            services.Configure<FakeOptions>(o =>
+            {
+                o.Configured = true;
+                o.Environment = "ConfigureCaseInsensitiveServices";
+            });
+            return services.BuildServiceProvider();
+        }
+
+        public void ConfigureCaseInsensitive(IApplicationBuilder app)
+        {
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/test/Fakes/StartupConfigureServicesThrows.cs b/src/Hosting/Hosting/test/Fakes/StartupConfigureServicesThrows.cs
new file mode 100644
index 0000000000000000000000000000000000000000..895fa654e9e371d86da31296024f6396b08aec28
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupConfigureServicesThrows.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 System;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class StartupConfigureServicesThrows
+    {
+        public void ConfigureServices(IServiceCollection services)
+        {
+            throw new Exception("Exception from ConfigureServices");
+        }
+
+        public void Configure(IApplicationBuilder builder)
+        {
+
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/StartupConfigureThrows.cs b/src/Hosting/Hosting/test/Fakes/StartupConfigureThrows.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1d9fa8ef37b3523a777b157f5833743321fafafa
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupConfigureThrows.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.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class StartupConfigureThrows
+    {
+        public void ConfigureServices(IServiceCollection services)
+        {
+        }
+
+        public void Configure(IApplicationBuilder builder)
+        {
+            throw new Exception("Exception from Configure");
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/StartupCtorThrows.cs b/src/Hosting/Hosting/test/Fakes/StartupCtorThrows.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b7c1f223d323343690fa2d3277da3e10f480b4ef
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupCtorThrows.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 System;
+using Microsoft.AspNetCore.Builder;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class StartupCtorThrows
+    {
+        public StartupCtorThrows()
+        {
+            throw new Exception("Exception from constructor");
+        }
+
+        public void Configure(IApplicationBuilder app)
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/StartupNoServices.cs b/src/Hosting/Hosting/test/Fakes/StartupNoServices.cs
new file mode 100644
index 0000000000000000000000000000000000000000..93e054fbc6e1a804962daaaafe975a3834ca059a
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupNoServices.cs
@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Builder;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class StartupNoServices : Hosting.StartupBase
+    {
+        public StartupNoServices()
+        {
+        }
+
+        public override void Configure(IApplicationBuilder builder)
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/StartupPrivateConfigure.cs b/src/Hosting/Hosting/test/Fakes/StartupPrivateConfigure.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e421ba08c73911bc660ec7fee8780a30f468a871
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupPrivateConfigure.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 Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class StartupPrivateConfigure
+    {
+        public StartupPrivateConfigure()
+        {
+        }
+        
+        public void ConfigureServices(IServiceCollection services)
+        {
+
+        }
+
+        private void Configure(IApplicationBuilder builder)
+        {
+
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/StartupStaticCtorThrows.cs b/src/Hosting/Hosting/test/Fakes/StartupStaticCtorThrows.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c9164fa98ff732982e7f554e76536baca6d55474
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupStaticCtorThrows.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 System;
+using Microsoft.AspNetCore.Builder;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class StartupStaticCtorThrows
+    {
+        static StartupStaticCtorThrows()
+        {
+            throw new Exception("Exception from static constructor");
+        }
+
+        public void Configure(IApplicationBuilder app)
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/StartupThrowTypeLoadException.cs b/src/Hosting/Hosting/test/Fakes/StartupThrowTypeLoadException.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b3cbd602259cb148608b080b6ccef0f6aa69a149
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupThrowTypeLoadException.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.IO;
+using System.Reflection;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class StartupThrowTypeLoadException
+    {
+        public StartupThrowTypeLoadException()
+        {
+            // For this exception, the error page should contain details of the LoaderExceptions 
+            throw new ReflectionTypeLoadException(
+                classes: new Type[] { GetType() },
+                exceptions: new Exception[] { new FileNotFoundException("Message from the LoaderException") },
+                message: "This should not be in the output");
+        }
+
+        public void Configure()
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/StartupTwoConfigureServices.cs b/src/Hosting/Hosting/test/Fakes/StartupTwoConfigureServices.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e7c1be78f927af489757b6ff1d657b76c0e6a54e
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupTwoConfigureServices.cs
@@ -0,0 +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 Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class StartupTwoConfigureServices
+    {
+        public StartupTwoConfigureServices()
+        {
+        }
+
+        public void ConfigureServices(IServiceCollection services)
+        {
+
+        }
+
+        public void ConfigureServices(IServiceCollection services, object service)
+        {
+
+        }
+
+        public void Configure(IApplicationBuilder builder)
+        {
+
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/StartupTwoConfigures.cs b/src/Hosting/Hosting/test/Fakes/StartupTwoConfigures.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ce4132ac136614eb020c7abb4bf013de997a492c
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupTwoConfigures.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Builder;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class StartupTwoConfigures
+    {
+        public StartupTwoConfigures()
+        {
+        }
+
+        public void Configure(IApplicationBuilder builder)
+        {
+
+        }
+
+        public void Configure(IApplicationBuilder builder, object service)
+        {
+
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/StartupWithConfigureServices.cs b/src/Hosting/Hosting/test/Fakes/StartupWithConfigureServices.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4c397fe8bdd6b4419cf073b5521f1191c230ca93
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupWithConfigureServices.cs
@@ -0,0 +1,35 @@
+using System;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class StartupWithConfigureServices
+    {
+        public void ConfigureServices(IServiceCollection services)
+        {
+            services.AddSingleton<IFoo, Foo>();
+        }
+
+        public void Configure(IApplicationBuilder app, IFoo foo)
+        {
+            foo.Bar();
+        }
+
+        public interface IFoo
+        {
+            bool Invoked { get; }
+            void Bar();
+        }
+
+        public class Foo : IFoo
+        {
+            public bool Invoked { get; private set; }
+
+            public void Bar()
+            {
+                Invoked = true;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/StartupWithConfigureServicesNotResolved.cs b/src/Hosting/Hosting/test/Fakes/StartupWithConfigureServicesNotResolved.cs
new file mode 100644
index 0000000000000000000000000000000000000000..bff10f94421b97e607cec41e1f9af7218c07f5b8
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupWithConfigureServicesNotResolved.cs
@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Builder;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class StartupWithConfigureServicesNotResolved
+    {
+        public StartupWithConfigureServicesNotResolved()
+        {
+        }
+
+        public void Configure(IApplicationBuilder builder, int notAService)
+        {
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/test/Fakes/StartupWithHostingEnvironment.cs b/src/Hosting/Hosting/test/Fakes/StartupWithHostingEnvironment.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c4a57765028d57fb44ed85a22ccf96452553b159
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupWithHostingEnvironment.cs
@@ -0,0 +1,18 @@
+using System;
+using Microsoft.AspNetCore.Builder;
+
+namespace Microsoft.AspNetCore.Hosting.Tests.Fakes
+{
+    public class StartupWithHostingEnvironment
+    {
+        public StartupWithHostingEnvironment(IHostingEnvironment env)
+        {
+            env.EnvironmentName = "Changed";
+        }
+
+        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+        {
+
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/StartupWithILoggerFactory.cs b/src/Hosting/Hosting/test/Fakes/StartupWithILoggerFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ad596e2546169c1e81e5a48f8b3a19d04cbe5f67
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupWithILoggerFactory.cs
@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class StartupWithILoggerFactory
+    {
+        public ILoggerFactory ConstructorLoggerFactory { get; set; }
+
+        public ILoggerFactory ConfigureLoggerFactory { get; set; }
+
+        public StartupWithILoggerFactory(ILoggerFactory constructorLoggerFactory)
+        {
+             ConstructorLoggerFactory = constructorLoggerFactory;
+        }
+
+        public void ConfigureServices(IServiceCollection collection)
+        {
+            collection.AddSingleton(this);
+        }
+
+        public void Configure(IApplicationBuilder builder, ILoggerFactory loggerFactory)
+        {
+            ConfigureLoggerFactory = loggerFactory;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/StartupWithNullConfigureServices.cs b/src/Hosting/Hosting/test/Fakes/StartupWithNullConfigureServices.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9390f4727fa4f86257a72019199125d03cacddb1
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupWithNullConfigureServices.cs
@@ -0,0 +1,16 @@
+using System;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class StartupWithNullConfigureServices
+    {
+        public IServiceProvider ConfigureServices(IServiceCollection services)
+        {
+            return null;
+        }
+
+        public void Configure(IApplicationBuilder app) { }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/Fakes/StartupWithScopedServices.cs b/src/Hosting/Hosting/test/Fakes/StartupWithScopedServices.cs
new file mode 100644
index 0000000000000000000000000000000000000000..985f920473fe3b15a7abb08d6c23acf58c438410
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupWithScopedServices.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.AspNetCore.Builder;
+using static Microsoft.AspNetCore.Hosting.Tests.StartupManagerTests;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class StartupWithScopedServices
+    {
+        public DisposableService DisposableService { get; set; }
+
+        public void Configure(IApplicationBuilder builder, DisposableService disposable)
+        {
+            DisposableService = disposable;
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/test/Fakes/StartupWithServices.cs b/src/Hosting/Hosting/test/Fakes/StartupWithServices.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7056b37d687f43549dd0b87b3bd0510bdd69c31e
--- /dev/null
+++ b/src/Hosting/Hosting/test/Fakes/StartupWithServices.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Builder;
+
+namespace Microsoft.AspNetCore.Hosting.Fakes
+{
+    public class StartupWithServices
+    {
+        private readonly IFakeStartupCallback _fakeStartupCallback;
+
+        public StartupWithServices(IFakeStartupCallback fakeStartupCallback)
+        {
+            _fakeStartupCallback = fakeStartupCallback;
+        }
+
+        public void Configure(IApplicationBuilder builder, IFakeStartupCallback fakeStartupCallback2)
+        {
+            _fakeStartupCallback.ConfigurationMethodCalled(this);
+            fakeStartupCallback2.ConfigurationMethodCalled(this);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/HostingApplicationTests.cs b/src/Hosting/Hosting/test/HostingApplicationTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..de1dc01899f9ef61fa568c1ee8f0ff4b286fe89e
--- /dev/null
+++ b/src/Hosting/Hosting/test/HostingApplicationTests.cs
@@ -0,0 +1,371 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting.Internal;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.ObjectPool;
+using Microsoft.Extensions.Options;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Hosting.Tests
+{
+    public class HostingApplicationTests
+    {
+        [Fact]
+        public void DisposeContextDoesNotThrowWhenContextScopeIsNull()
+        {
+            // Arrange
+            var hostingApplication = CreateApplication(out var features);
+            var context = hostingApplication.CreateContext(features);
+
+            // Act/Assert
+            hostingApplication.DisposeContext(context, null);
+        }
+
+        [Fact]
+        public void CreateContextSetsCorrelationIdInScope()
+        {
+            // Arrange
+            var logger = new LoggerWithScopes();
+            var hostingApplication = CreateApplication(out var features, logger: logger);
+            features.Get<IHttpRequestFeature>().Headers["Request-Id"] = "some correlation id";
+
+            // Act
+            var context = hostingApplication.CreateContext(features);
+
+            Assert.Single(logger.Scopes);
+            var pairs = ((IReadOnlyList<KeyValuePair<string, object>>)logger.Scopes[0]).ToDictionary(p => p.Key, p => p.Value);
+            Assert.Equal("some correlation id", pairs["CorrelationId"].ToString());
+        }
+
+        [Fact]
+        public void ActivityIsNotCreatedWhenIsEnabledForActivityIsFalse()
+        {
+            var diagnosticSource = new DiagnosticListener("DummySource");
+            var hostingApplication = CreateApplication(out var features, diagnosticSource: diagnosticSource);
+
+            bool eventsFired = false;
+            bool isEnabledActivityFired = false;
+            bool isEnabledStartFired = false;
+
+            diagnosticSource.Subscribe(new CallbackDiagnosticListener(pair =>
+            {
+                eventsFired |= pair.Key.StartsWith("Microsoft.AspNetCore.Hosting.HttpRequestIn");
+            }), (s, o, arg3) =>
+            {
+                if (s == "Microsoft.AspNetCore.Hosting.HttpRequestIn")
+                {
+                    Assert.IsAssignableFrom<HttpContext>(o);
+                    isEnabledActivityFired = true;
+                }
+                if (s == "Microsoft.AspNetCore.Hosting.HttpRequestIn.Start")
+                {
+                    isEnabledStartFired = true;
+                }
+                return false;
+            });
+
+            hostingApplication.CreateContext(features);
+            Assert.Null(Activity.Current);
+            Assert.True(isEnabledActivityFired);
+            Assert.False(isEnabledStartFired);
+            Assert.False(eventsFired);
+        }
+
+        [Fact]
+        public void ActivityIsCreatedButNotLoggedWhenIsEnabledForActivityStartIsFalse()
+        {
+            var diagnosticSource = new DiagnosticListener("DummySource");
+            var hostingApplication = CreateApplication(out var features, diagnosticSource: diagnosticSource);
+
+            bool eventsFired = false;
+            bool isEnabledStartFired = false;
+            bool isEnabledActivityFired = false;
+
+            diagnosticSource.Subscribe(new CallbackDiagnosticListener(pair =>
+            {
+                eventsFired |= pair.Key.StartsWith("Microsoft.AspNetCore.Hosting.HttpRequestIn");
+            }), (s, o, arg3) =>
+            {
+                if (s == "Microsoft.AspNetCore.Hosting.HttpRequestIn")
+                {
+                    Assert.IsAssignableFrom<HttpContext>(o);
+                    isEnabledActivityFired = true;
+                    return true;
+                }
+
+                if (s == "Microsoft.AspNetCore.Hosting.HttpRequestIn.Start")
+                {
+                    isEnabledStartFired = true;
+                    return false;
+                }
+                return true;
+            });
+
+            hostingApplication.CreateContext(features);
+            Assert.NotNull(Activity.Current);
+            Assert.True(isEnabledActivityFired);
+            Assert.True(isEnabledStartFired);
+            Assert.False(eventsFired);
+        }
+
+        [Fact]
+        public void ActivityIsCreatedAndLogged()
+        {
+            var diagnosticSource = new DiagnosticListener("DummySource");
+            var hostingApplication = CreateApplication(out var features, diagnosticSource: diagnosticSource);
+
+            bool startCalled = false;
+
+            diagnosticSource.Subscribe(new CallbackDiagnosticListener(pair =>
+            {
+                if (pair.Key == "Microsoft.AspNetCore.Hosting.HttpRequestIn.Start")
+                {
+                    startCalled = true;
+                    Assert.NotNull(pair.Value);
+                    Assert.NotNull(Activity.Current);
+                    Assert.Equal("Microsoft.AspNetCore.Hosting.HttpRequestIn", Activity.Current.OperationName);
+                    AssertProperty<HttpContext>(pair.Value, "HttpContext");
+                }
+            }));
+
+            hostingApplication.CreateContext(features);
+            Assert.NotNull(Activity.Current);
+            Assert.True(startCalled);
+        }
+
+        [Fact]
+        public void ActivityIsStoppedDuringStopCall()
+        {
+            var diagnosticSource = new DiagnosticListener("DummySource");
+            var hostingApplication = CreateApplication(out var features, diagnosticSource: diagnosticSource);
+
+            bool endCalled = false;
+            diagnosticSource.Subscribe(new CallbackDiagnosticListener(pair =>
+            {
+                if (pair.Key == "Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop")
+                {
+                    endCalled = true;
+
+                    Assert.NotNull(Activity.Current);
+                    Assert.True(Activity.Current.Duration > TimeSpan.Zero);
+                    Assert.Equal("Microsoft.AspNetCore.Hosting.HttpRequestIn", Activity.Current.OperationName);
+                    AssertProperty<HttpContext>(pair.Value, "HttpContext");
+                }
+            }));
+
+            var context = hostingApplication.CreateContext(features);
+            hostingApplication.DisposeContext(context, null);
+            Assert.True(endCalled);
+        }
+
+        [Fact]
+        public void ActivityIsStoppedDuringUnhandledExceptionCall()
+        {
+            var diagnosticSource = new DiagnosticListener("DummySource");
+            var hostingApplication = CreateApplication(out var features, diagnosticSource: diagnosticSource);
+
+            bool endCalled = false;
+            diagnosticSource.Subscribe(new CallbackDiagnosticListener(pair =>
+            {
+                if (pair.Key == "Microsoft.AspNetCore.Hosting.HttpRequestIn.Stop")
+                {
+                    endCalled = true;
+                    Assert.NotNull(Activity.Current);
+                    Assert.True(Activity.Current.Duration > TimeSpan.Zero);
+                    Assert.Equal("Microsoft.AspNetCore.Hosting.HttpRequestIn", Activity.Current.OperationName);
+                    AssertProperty<HttpContext>(pair.Value, "HttpContext");
+                }
+            }));
+
+            var context = hostingApplication.CreateContext(features);
+            hostingApplication.DisposeContext(context, new Exception());
+            Assert.True(endCalled);
+        }
+
+        [Fact]
+        public void ActivityIsAvailableDuringUnhandledExceptionCall()
+        {
+            var diagnosticSource = new DiagnosticListener("DummySource");
+            var hostingApplication = CreateApplication(out var features, diagnosticSource: diagnosticSource);
+
+            bool endCalled = false;
+            diagnosticSource.Subscribe(new CallbackDiagnosticListener(pair =>
+            {
+                if (pair.Key == "Microsoft.AspNetCore.Hosting.UnhandledException")
+                {
+                    endCalled = true;
+                    Assert.NotNull(Activity.Current);
+                    Assert.Equal("Microsoft.AspNetCore.Hosting.HttpRequestIn", Activity.Current.OperationName);
+                }
+            }));
+
+            var context = hostingApplication.CreateContext(features);
+            hostingApplication.DisposeContext(context, new Exception());
+            Assert.True(endCalled);
+        }
+
+        [Fact]
+        public void ActivityIsAvailibleDuringRequest()
+        {
+            var diagnosticSource = new DiagnosticListener("DummySource");
+            var hostingApplication = CreateApplication(out var features, diagnosticSource: diagnosticSource);
+
+            diagnosticSource.Subscribe(new CallbackDiagnosticListener(pair => { }),
+                s =>
+                {
+                    if (s.StartsWith("Microsoft.AspNetCore.Hosting.HttpRequestIn"))
+                    {
+                        return true;
+                    }
+                    return false;
+                });
+
+            hostingApplication.CreateContext(features);
+
+            Assert.NotNull(Activity.Current);
+            Assert.Equal("Microsoft.AspNetCore.Hosting.HttpRequestIn", Activity.Current.OperationName);
+        }
+
+        [Fact]
+        public void ActivityParentIdAndBaggeReadFromHeaders()
+        {
+            var diagnosticSource = new DiagnosticListener("DummySource");
+            var hostingApplication = CreateApplication(out var features, diagnosticSource: diagnosticSource);
+
+            diagnosticSource.Subscribe(new CallbackDiagnosticListener(pair => { }),
+                s =>
+                {
+                    if (s.StartsWith("Microsoft.AspNetCore.Hosting.HttpRequestIn"))
+                    {
+                        return true;
+                    }
+                    return false;
+                });
+
+            features.Set<IHttpRequestFeature>(new HttpRequestFeature()
+            {
+                Headers = new HeaderDictionary()
+                {
+                    {"Request-Id", "ParentId1"},
+                    {"Correlation-Context", "Key1=value1, Key2=value2"}
+                }
+            });
+            hostingApplication.CreateContext(features);
+            Assert.Equal("Microsoft.AspNetCore.Hosting.HttpRequestIn", Activity.Current.OperationName);
+            Assert.Equal("ParentId1", Activity.Current.ParentId);
+            Assert.Contains(Activity.Current.Baggage, pair => pair.Key == "Key1" && pair.Value == "value1");
+            Assert.Contains(Activity.Current.Baggage, pair => pair.Key == "Key2" && pair.Value == "value2");
+        }
+
+        private static void AssertProperty<T>(object o, string name)
+        {
+            Assert.NotNull(o);
+            var property = o.GetType().GetTypeInfo().GetProperty(name, BindingFlags.Instance | BindingFlags.Public);
+            Assert.NotNull(property);
+            var value = property.GetValue(o);
+            Assert.NotNull(value);
+            Assert.IsAssignableFrom<T>(value);
+        }
+
+        private static HostingApplication CreateApplication(out FeatureCollection features,
+            DiagnosticListener diagnosticSource = null, ILogger logger = null)
+        {
+            var httpContextFactory = new Mock<IHttpContextFactory>();
+
+            features = new FeatureCollection();
+            features.Set<IHttpRequestFeature>(new HttpRequestFeature());
+            httpContextFactory.Setup(s => s.Create(It.IsAny<IFeatureCollection>())).Returns(new DefaultHttpContext(features));
+            httpContextFactory.Setup(s => s.Dispose(It.IsAny<HttpContext>()));
+
+            var hostingApplication = new HostingApplication(
+                ctx => Task.FromResult(0),
+                logger ?? new NullScopeLogger(),
+                diagnosticSource ?? new NoopDiagnosticSource(),
+                httpContextFactory.Object);
+
+            return hostingApplication;
+        }
+
+        private class NullScopeLogger : ILogger
+        {
+            public IDisposable BeginScope<TState>(TState state) => null;
+
+            public bool IsEnabled(LogLevel logLevel) => true;
+
+            public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
+            {
+            }
+        }
+
+        private class LoggerWithScopes : ILogger
+        {
+            public IDisposable BeginScope<TState>(TState state)
+            {
+                Scopes.Add(state);
+                return new Scope();
+            }
+
+            public List<object> Scopes { get; set; } = new List<object>();
+
+            public bool IsEnabled(LogLevel logLevel) => true;
+
+            public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
+            {
+                
+            }
+
+            private class Scope : IDisposable
+            {
+                public void Dispose()
+                {
+                }
+            }
+        }
+
+        private class NoopDiagnosticSource : DiagnosticListener
+        {
+            public NoopDiagnosticSource() : base("DummyListener")
+            {
+            }
+
+            public override bool IsEnabled(string name) => true;
+
+            public override void Write(string name, object value)
+            {
+            }
+        }
+
+        private class CallbackDiagnosticListener : IObserver<KeyValuePair<string, object>>
+        {
+            private readonly Action<KeyValuePair<string, object>> _callback;
+
+            public CallbackDiagnosticListener(Action<KeyValuePair<string, object>> callback)
+            {
+                _callback = callback;
+            }
+
+            public void OnNext(KeyValuePair<string, object> value)
+            {
+                _callback(value);
+            }
+
+            public void OnError(Exception error)
+            {
+            }
+
+            public void OnCompleted()
+            {
+            }
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/test/HostingEnvironmentExtensionsTests.cs b/src/Hosting/Hosting/test/HostingEnvironmentExtensionsTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3a646b005b73d13a338219433a282299f790c71a
--- /dev/null
+++ b/src/Hosting/Hosting/test/HostingEnvironmentExtensionsTests.cs
@@ -0,0 +1,63 @@
+// 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 Microsoft.AspNetCore.Hosting.Internal;
+using Microsoft.Extensions.FileProviders;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Hosting.Tests
+{
+    public class HostingEnvironmentExtensionsTests
+    {
+        [Fact]
+        public void SetsFullPathToWwwroot()
+        {
+            var env = new HostingEnvironment();
+
+            env.Initialize(Path.GetFullPath("."), new WebHostOptions() { WebRoot = "testroot" });
+
+            Assert.Equal(Path.GetFullPath("."), env.ContentRootPath);
+            Assert.Equal(Path.GetFullPath("testroot"), env.WebRootPath);
+            Assert.IsAssignableFrom<PhysicalFileProvider>(env.ContentRootFileProvider);
+            Assert.IsAssignableFrom<PhysicalFileProvider>(env.WebRootFileProvider);
+        }
+
+        [Fact]
+        public void DefaultsToWwwrootSubdir()
+        {
+            var env = new HostingEnvironment();
+
+            env.Initialize(Path.GetFullPath("testroot"), new WebHostOptions());
+
+            Assert.Equal(Path.GetFullPath("testroot"), env.ContentRootPath);
+            Assert.Equal(Path.GetFullPath(Path.Combine("testroot", "wwwroot")), env.WebRootPath);
+            Assert.IsAssignableFrom<PhysicalFileProvider>(env.ContentRootFileProvider);
+            Assert.IsAssignableFrom<PhysicalFileProvider>(env.WebRootFileProvider);
+        }
+
+        [Fact]
+        public void DefaultsToNullFileProvider()
+        {
+            var env = new HostingEnvironment();
+
+            env.Initialize(Path.GetFullPath(Path.Combine("testroot", "wwwroot")), new WebHostOptions());
+
+            Assert.Equal(Path.GetFullPath(Path.Combine("testroot", "wwwroot")), env.ContentRootPath);
+            Assert.Null(env.WebRootPath);
+            Assert.IsAssignableFrom<PhysicalFileProvider>(env.ContentRootFileProvider);
+            Assert.IsAssignableFrom<NullFileProvider>(env.WebRootFileProvider);
+        }
+
+        [Fact]
+        public void OverridesEnvironmentFromConfig()
+        {
+            var env = new HostingEnvironment();
+            env.EnvironmentName = "SomeName";
+
+            env.Initialize(Path.GetFullPath("."), new WebHostOptions() { Environment = "NewName" });
+
+            Assert.Equal("NewName", env.EnvironmentName);
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/test/Internal/HostingEventSourceTests.cs b/src/Hosting/Hosting/test/Internal/HostingEventSourceTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..32b51d8a9560f388fe4a1f64d18a894a985b92b9
--- /dev/null
+++ b/src/Hosting/Hosting/test/Internal/HostingEventSourceTests.cs
@@ -0,0 +1,217 @@
+// 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.Tracing;
+using System.Reflection;
+using Microsoft.AspNetCore.Http;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Hosting.Internal
+{
+    public class HostingEventSourceTests
+    {
+        [Fact]
+        public void MatchesNameAndGuid()
+        {
+            // Arrange & Act
+            var eventSourceType = typeof(WebHost).GetTypeInfo().Assembly.GetType(
+                "Microsoft.AspNetCore.Hosting.Internal.HostingEventSource",
+                throwOnError: true,
+                ignoreCase: false);
+
+            // Assert
+            Assert.NotNull(eventSourceType);
+            Assert.Equal("Microsoft-AspNetCore-Hosting", EventSource.GetName(eventSourceType));
+            Assert.Equal(Guid.Parse("9e620d2a-55d4-5ade-deb7-c26046d245a8"), EventSource.GetGuid(eventSourceType));
+            Assert.NotEmpty(EventSource.GenerateManifest(eventSourceType, "assemblyPathToIncludeInManifest"));
+        }
+
+        [Fact]
+        public void HostStart()
+        {
+            // Arrange
+            var expectedEventId = 1;
+            var eventListener = new TestEventListener(expectedEventId);
+            var hostingEventSource = HostingEventSource.Log;
+            eventListener.EnableEvents(hostingEventSource, EventLevel.Informational);
+
+            // Act
+            hostingEventSource.HostStart();
+
+            // Assert
+            var eventData = eventListener.EventData;
+            Assert.NotNull(eventData);
+            Assert.Equal(expectedEventId, eventData.EventId);
+            Assert.Equal("HostStart", eventData.EventName);
+            Assert.Equal(EventLevel.Informational, eventData.Level);
+            Assert.Same(hostingEventSource, eventData.EventSource);
+            Assert.Null(eventData.Message);
+            Assert.Empty(eventData.Payload);
+        }
+
+        [Fact]
+        public void HostStop()
+        {
+            // Arrange
+            var expectedEventId = 2;
+            var eventListener = new TestEventListener(expectedEventId);
+            var hostingEventSource = HostingEventSource.Log;
+            eventListener.EnableEvents(hostingEventSource, EventLevel.Informational);
+
+            // Act
+            hostingEventSource.HostStop();
+
+            // Assert
+            var eventData = eventListener.EventData;
+            Assert.NotNull(eventData);
+            Assert.Equal(expectedEventId, eventData.EventId);
+            Assert.Equal("HostStop", eventData.EventName);
+            Assert.Equal(EventLevel.Informational, eventData.Level);
+            Assert.Same(hostingEventSource, eventData.EventSource);
+            Assert.Null(eventData.Message);
+            Assert.Empty(eventData.Payload);
+        }
+
+        public static TheoryData<DefaultHttpContext, string[]> RequestStartData
+        {
+            get
+            {
+                var variations = new TheoryData<DefaultHttpContext, string[]>();
+
+                var context = new DefaultHttpContext();
+                context.Request.Method = "GET";
+                context.Request.Path = "/Home/Index";
+                variations.Add(
+                    context,
+                    new string[]
+                    {
+                        "GET",
+                        "/Home/Index"
+                    });
+
+                context = new DefaultHttpContext();
+                context.Request.Method = "POST";
+                context.Request.Path = "/";
+                variations.Add(
+                    context,
+                    new string[]
+                    {
+                        "POST",
+                        "/"
+                    });
+
+                return variations;
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(RequestStartData))]
+        public void RequestStart(DefaultHttpContext httpContext, string[] expected)
+        {
+            // Arrange
+            var expectedEventId = 3;
+            var eventListener = new TestEventListener(expectedEventId);
+            var hostingEventSource = HostingEventSource.Log;
+            eventListener.EnableEvents(hostingEventSource, EventLevel.Informational);
+
+            // Act
+            hostingEventSource.RequestStart(httpContext.Request.Method, httpContext.Request.Path);
+
+            // Assert
+            var eventData = eventListener.EventData;
+            Assert.NotNull(eventData);
+            Assert.Equal(expectedEventId, eventData.EventId);
+            Assert.Equal("RequestStart", eventData.EventName);
+            Assert.Equal(EventLevel.Informational, eventData.Level);
+            Assert.Same(hostingEventSource, eventData.EventSource);
+            Assert.Null(eventData.Message);
+
+            var payloadList = eventData.Payload;
+            Assert.Equal(expected.Length, payloadList.Count);
+            for (var i = 0; i < expected.Length; i++)
+            {
+                Assert.Equal(expected[i], payloadList[i]);
+            }
+        }
+
+        [Fact]
+        public void RequestStop()
+        {
+            // Arrange
+            var expectedEventId = 4;
+            var eventListener = new TestEventListener(expectedEventId);
+            var hostingEventSource = HostingEventSource.Log;
+            eventListener.EnableEvents(hostingEventSource, EventLevel.Informational);
+
+            // Act
+            hostingEventSource.RequestStop();
+
+            // Assert
+            var eventData = eventListener.EventData;
+            Assert.Equal(expectedEventId, eventData.EventId);
+            Assert.Equal("RequestStop", eventData.EventName);
+            Assert.Equal(EventLevel.Informational, eventData.Level);
+            Assert.Same(hostingEventSource, eventData.EventSource);
+            Assert.Null(eventData.Message);
+            Assert.Empty(eventData.Payload);
+        }
+
+        [Fact]
+        public void UnhandledException()
+        {
+            // Arrange
+            var expectedEventId = 5;
+            var eventListener = new TestEventListener(expectedEventId);
+            var hostingEventSource = HostingEventSource.Log;
+            eventListener.EnableEvents(hostingEventSource, EventLevel.Informational);
+
+            // Act
+            hostingEventSource.UnhandledException();
+
+            // Assert
+            var eventData = eventListener.EventData;
+            Assert.Equal(expectedEventId, eventData.EventId);
+            Assert.Equal("UnhandledException", eventData.EventName);
+            Assert.Equal(EventLevel.Error, eventData.Level);
+            Assert.Same(hostingEventSource, eventData.EventSource);
+            Assert.Null(eventData.Message);
+            Assert.Empty(eventData.Payload);
+        }
+
+        private static Exception GetException()
+        {
+            try
+            {
+                throw new InvalidOperationException("An invalid operation has occurred");
+            }
+            catch (Exception ex)
+            {
+                return ex;
+            }
+        }
+
+        private class TestEventListener : EventListener
+        {
+            private readonly int _eventId;
+
+            public TestEventListener(int eventId)
+            {
+                _eventId = eventId;
+            }
+
+            public EventWrittenEventArgs EventData { get; private set; }
+
+            protected override void OnEventWritten(EventWrittenEventArgs eventData)
+            {
+                // The tests here run in parallel and since the single publisher instance (HostingEventingSource)
+                // notifies all listener instances in these tests, capture the EventData that a test is explicitly
+                // looking for and not give back other tests' data.
+                if (eventData.EventId == _eventId)
+                {
+                    EventData = eventData;
+                }
+            }
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/test/Internal/HostingRequestStartLogTests.cs b/src/Hosting/Hosting/test/Internal/HostingRequestStartLogTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ca8e25ed9e0630bb0d45f94d0a19460dcf4cb12a
--- /dev/null
+++ b/src/Hosting/Hosting/test/Internal/HostingRequestStartLogTests.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Hosting.Internal;
+using Moq;
+using Xunit;
+namespace Microsoft.AspNetCore.Hosting.Tests.Internal
+{
+    public class HostingRequestStartLogTests
+    {
+        [Theory]
+        [InlineData(",XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "Request starting GET 1.1 http://,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX//?query test 0")]
+        [InlineData(" XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "Request starting GET 1.1 http:// XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX//?query test 0")]
+        public void InvalidHttpContext_DoesNotThrowOnAccessingProperties(string input, string expected)
+        {
+            var mockRequest = new Mock<HttpRequest>();
+            mockRequest.Setup(request => request.Protocol).Returns("GET");
+            mockRequest.Setup(request => request.Method).Returns("1.1");
+            mockRequest.Setup(request => request.Scheme).Returns("http");
+            mockRequest.Setup(request => request.Host).Returns(new HostString(input));
+            mockRequest.Setup(request => request.PathBase).Returns(new PathString("/"));
+            mockRequest.Setup(request => request.Path).Returns(new PathString("/"));
+            mockRequest.Setup(request => request.QueryString).Returns(new QueryString("?query"));
+            mockRequest.Setup(request => request.ContentType).Returns("test");
+            mockRequest.Setup(request => request.ContentLength).Returns(0);
+
+            var mockContext = new Mock<HttpContext>();
+            mockContext.Setup(context => context.Request).Returns(mockRequest.Object);
+
+            var logger = new HostingRequestStartingLog(mockContext.Object);
+            Assert.Equal(expected, logger.ToString());
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/test/Internal/MyBadContainerFactory.cs b/src/Hosting/Hosting/test/Internal/MyBadContainerFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..058abc894a8c399581b9b3fd5c2c5c9d33158af0
--- /dev/null
+++ b/src/Hosting/Hosting/test/Internal/MyBadContainerFactory.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Tests.Internal
+{
+    public class MyBadContainerFactory : IServiceProviderFactory<MyContainer>
+    {
+        public MyContainer CreateBuilder(IServiceCollection services)
+        {
+            var container = new MyContainer();
+            container.Populate(services);
+            return container;
+        }
+
+        public IServiceProvider CreateServiceProvider(MyContainer containerBuilder)
+        {
+            containerBuilder.Build();
+            return null;
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/test/Internal/MyContainer.cs b/src/Hosting/Hosting/test/Internal/MyContainer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5fbffaad1b8cb5353d7ec4f1483b04b494570dda
--- /dev/null
+++ b/src/Hosting/Hosting/test/Internal/MyContainer.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Tests.Internal
+{
+    public class MyContainer : IServiceProvider
+    {
+        private IServiceProvider _inner;
+        private IServiceCollection _services;
+
+        public bool FancyMethodCalled { get; private set; }
+
+        public IServiceCollection Services => _services;
+
+        public string Environment { get; set; }
+
+        public object GetService(Type serviceType)
+        {
+            return _inner.GetService(serviceType);
+        }
+
+        public void Populate(IServiceCollection services)
+        {
+            _services = services;
+        }
+
+        public void Build()
+        {
+            _inner = _services.BuildServiceProvider();
+        }
+
+        public void MyFancyContainerMethod()
+        {
+            FancyMethodCalled = true;
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/test/Internal/MyContainerFactory.cs b/src/Hosting/Hosting/test/Internal/MyContainerFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..06a1ad614b312e71505005114324583b2d216786
--- /dev/null
+++ b/src/Hosting/Hosting/test/Internal/MyContainerFactory.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.Tests.Internal
+{
+    public class MyContainerFactory : IServiceProviderFactory<MyContainer>
+    {
+        public MyContainer CreateBuilder(IServiceCollection services)
+        {
+            var container = new MyContainer();
+            container.Populate(services);
+            return container;
+        }
+
+        public IServiceProvider CreateServiceProvider(MyContainer containerBuilder)
+        {
+            containerBuilder.Build();
+            return containerBuilder;
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/test/Microsoft.AspNetCore.Hosting.Tests.csproj b/src/Hosting/Hosting/test/Microsoft.AspNetCore.Hosting.Tests.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..151a66777adbd1c7eaa6aa77b69ace28af152e3f
--- /dev/null
+++ b/src/Hosting/Hosting/test/Microsoft.AspNetCore.Hosting.Tests.csproj
@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Content Include="testroot\**\*" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\test\testassets\TestStartupAssembly1\TestStartupAssembly1.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Owin" />
+    <Reference Include="Microsoft.AspNetCore.Hosting" />
+    <Reference Include="Microsoft.Extensions.Logging.Testing" />
+    <Reference Include="Microsoft.Extensions.Options" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Hosting/Hosting/test/RequestServicesContainerMiddlewareTests.cs b/src/Hosting/Hosting/test/RequestServicesContainerMiddlewareTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c153af4dc1fec0da864cf1fda02841cd4c5b17b1
--- /dev/null
+++ b/src/Hosting/Hosting/test/RequestServicesContainerMiddlewareTests.cs
@@ -0,0 +1,122 @@
+// 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.Reflection;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Builder.Internal;
+using Microsoft.AspNetCore.Hosting.Internal;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Hosting.Tests
+{
+    public class RequestServicesContainerMiddlewareTests
+    {
+        [Fact]
+        public async Task RequestServicesAreSet()
+        {
+            var serviceProvider = new ServiceCollection()
+                        .BuildServiceProvider();
+
+            var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
+
+            var middleware = new RequestServicesContainerMiddleware(
+                ctx => Task.CompletedTask,
+                scopeFactory);
+
+            var context = new DefaultHttpContext();
+            await middleware.Invoke(context);
+
+            Assert.NotNull(context.RequestServices);
+        }
+
+        [Fact]
+        public async Task RequestServicesAreNotOverwrittenIfAlreadySet()
+        {
+            var serviceProvider = new ServiceCollection()
+                        .BuildServiceProvider();
+
+            var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
+
+            var middleware = new RequestServicesContainerMiddleware(
+                ctx => Task.CompletedTask,
+                scopeFactory);
+
+            var context = new DefaultHttpContext();
+            context.RequestServices = serviceProvider;
+            await middleware.Invoke(context);
+
+            Assert.Same(serviceProvider, context.RequestServices);
+        }
+
+        [Fact]
+        public async Task RequestServicesAreDisposedOnCompleted()
+        {
+            var serviceProvider = new ServiceCollection()
+                        .AddTransient<DisposableThing>()
+                        .BuildServiceProvider();
+
+            var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
+            DisposableThing instance = null;
+
+            var middleware = new RequestServicesContainerMiddleware(
+                ctx =>
+                {
+                    instance = ctx.RequestServices.GetRequiredService<DisposableThing>();
+                    return Task.CompletedTask;
+                },
+                scopeFactory);
+
+            var context = new DefaultHttpContext();
+            var responseFeature = new TestHttpResponseFeature();
+            context.Features.Set<IHttpResponseFeature>(responseFeature);
+
+            await middleware.Invoke(context);
+
+            Assert.NotNull(context.RequestServices);
+            Assert.Single(responseFeature.CompletedCallbacks);
+
+            var callback = responseFeature.CompletedCallbacks[0];
+            await callback.callback(callback.state);
+
+            Assert.Null(context.RequestServices);
+            Assert.True(instance.Disposed);
+        }
+
+        private class DisposableThing : IDisposable
+        {
+            public bool Disposed { get; set; }
+            public void Dispose()
+            {
+                Disposed = true;
+            }
+        }
+
+        private class TestHttpResponseFeature : IHttpResponseFeature
+        {
+            public List<(Func<object, Task> callback, object state)> CompletedCallbacks = new List<(Func<object, Task> callback, object state)>();
+
+            public int StatusCode { get; set; }
+            public string ReasonPhrase { get; set; }
+            public IHeaderDictionary Headers { get; set; } = new HeaderDictionary();
+            public Stream Body { get; set; }
+
+            public bool HasStarted => false;
+
+            public void OnCompleted(Func<object, Task> callback, object state)
+            {
+                CompletedCallbacks.Add((callback, state));
+            }
+
+            public void OnStarting(Func<object, Task> callback, object state)
+            {
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/StartupManagerTests.cs b/src/Hosting/Hosting/test/StartupManagerTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..75d5ccb98c1ad0b6474e8c41ce1e1c02110f3f5d
--- /dev/null
+++ b/src/Hosting/Hosting/test/StartupManagerTests.cs
@@ -0,0 +1,735 @@
+// 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.Reflection;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Builder.Internal;
+using Microsoft.AspNetCore.Hosting.Fakes;
+using Microsoft.AspNetCore.Hosting.Internal;
+using Microsoft.AspNetCore.Hosting.Tests.Internal;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Hosting.Tests
+{
+    public class StartupManagerTests
+    {
+        [Fact]
+        public void ConventionalStartupClass_StartupServiceFilters_WrapsConfigureServicesMethod()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
+            serviceCollection.AddSingleton<IStartupConfigureServicesFilter>(new TestStartupServicesFilter(1, overrideAfterService: true));
+            serviceCollection.AddSingleton<IStartupConfigureServicesFilter>(new TestStartupServicesFilter(2, overrideAfterService: true));
+            var services = serviceCollection.BuildServiceProvider();
+
+            var type = typeof(VoidReturningStartupServicesFiltersStartup);
+            var startup = StartupLoader.LoadMethods(services, type, "");
+
+            var applicationServices = startup.ConfigureServicesDelegate(serviceCollection);
+            var before = applicationServices.GetRequiredService<ServiceBefore>();
+            var after = applicationServices.GetRequiredService<ServiceAfter>();
+
+            Assert.Equal("StartupServicesFilter Before 1", before.Message);
+            Assert.Equal("StartupServicesFilter After 1", after.Message);
+        }
+
+        [Fact]
+        public void ConventionalStartupClass_StartupServiceFilters_MultipleStartupServiceFiltersRun()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
+            serviceCollection.AddSingleton<IStartupConfigureServicesFilter>(new TestStartupServicesFilter(1, overrideAfterService: false));
+            serviceCollection.AddSingleton<IStartupConfigureServicesFilter>(new TestStartupServicesFilter(2, overrideAfterService: true));
+            var services = serviceCollection.BuildServiceProvider();
+
+            var type = typeof(VoidReturningStartupServicesFiltersStartup);
+            var startup = StartupLoader.LoadMethods(services, type, "");
+
+            var applicationServices = startup.ConfigureServicesDelegate(serviceCollection);
+            var before = applicationServices.GetRequiredService<ServiceBefore>();
+            var after = applicationServices.GetRequiredService<ServiceAfter>();
+
+            Assert.Equal("StartupServicesFilter Before 1", before.Message);
+            Assert.Equal("StartupServicesFilter After 2", after.Message);
+        }
+
+        [Fact]
+        public void ConventionalStartupClass_StartupServicesFilters_ThrowsIfStartupBuildsTheContainerAsync()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
+            serviceCollection.AddSingleton<IStartupConfigureServicesFilter>(new TestStartupServicesFilter(1, overrideAfterService: false));
+            var services = serviceCollection.BuildServiceProvider();
+
+            var type = typeof(IServiceProviderReturningStartupServicesFiltersStartup);
+            var startup = StartupLoader.LoadMethods(services, type, "");
+
+            var expectedMessage = $"A ConfigureServices method that returns an {nameof(IServiceProvider)} is " +
+                $"not compatible with the use of one or more {nameof(IStartupConfigureServicesFilter)}. " +
+                $"Use a void returning ConfigureServices method instead or a ConfigureContainer method.";
+
+            var exception = Assert.Throws<InvalidOperationException>(() => startup.ConfigureServicesDelegate(serviceCollection));
+
+            Assert.Equal(expectedMessage, exception.Message);
+        }
+
+        [Fact]
+        public void ConventionalStartupClass_ConfigureContainerFilters_WrapInRegistrationOrder()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<MyContainer>, MyContainerFactory>();
+            serviceCollection.AddSingleton<IStartupConfigureContainerFilter<MyContainer>>(new TestConfigureContainerFilter(1, overrideAfterService: true));
+            serviceCollection.AddSingleton<IStartupConfigureContainerFilter<MyContainer>>(new TestConfigureContainerFilter(2, overrideAfterService: true));
+            var services = serviceCollection.BuildServiceProvider();
+
+            var type = typeof(ConfigureContainerStartupServicesFiltersStartup);
+            var startup = StartupLoader.LoadMethods(services, type, "");
+
+            var applicationServices = startup.ConfigureServicesDelegate(serviceCollection);
+            var before = applicationServices.GetRequiredService<ServiceBefore>();
+            var after = applicationServices.GetRequiredService<ServiceAfter>();
+
+            Assert.Equal("ConfigureContainerFilter Before 1", before.Message);
+            Assert.Equal("ConfigureContainerFilter After 1", after.Message);
+        }
+
+        [Fact]
+        public void ConventionalStartupClass_ConfigureContainerFilters_RunsAllFilters()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<MyContainer>, MyContainerFactory>();
+            serviceCollection.AddSingleton<IStartupConfigureContainerFilter<MyContainer>>(new TestConfigureContainerFilter(1, overrideAfterService: false));
+            serviceCollection.AddSingleton<IStartupConfigureContainerFilter<MyContainer>>(new TestConfigureContainerFilter(2, overrideAfterService: true));
+            var services = serviceCollection.BuildServiceProvider();
+
+            var type = typeof(ConfigureContainerStartupServicesFiltersStartup);
+            var startup = StartupLoader.LoadMethods(services, type, "");
+
+            var applicationServices = startup.ConfigureServicesDelegate(serviceCollection);
+            var before = applicationServices.GetRequiredService<ServiceBefore>();
+            var after = applicationServices.GetRequiredService<ServiceAfter>();
+
+            Assert.Equal("ConfigureContainerFilter Before 1", before.Message);
+            Assert.Equal("ConfigureContainerFilter After 2", after.Message);
+        }
+
+        [Fact]
+        public void ConventionalStartupClass_ConfigureContainerFilters_RunAfterConfigureServicesFilters()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<MyContainer>, MyContainerFactory>();
+            serviceCollection.AddSingleton<IStartupConfigureServicesFilter>(new TestStartupServicesFilter(1, overrideAfterService: false));
+            serviceCollection.AddSingleton<IStartupConfigureContainerFilter<MyContainer>>(new TestConfigureContainerFilter(2, overrideAfterService: true));
+            var services = serviceCollection.BuildServiceProvider();
+
+            var type = typeof(ConfigureServicesAndConfigureContainerStartup);
+            var startup = StartupLoader.LoadMethods(services, type, "");
+
+            var applicationServices = startup.ConfigureServicesDelegate(serviceCollection);
+            var before = applicationServices.GetRequiredService<ServiceBefore>();
+            var after = applicationServices.GetRequiredService<ServiceAfter>();
+
+            Assert.Equal("StartupServicesFilter Before 1", before.Message);
+            Assert.Equal("ConfigureContainerFilter After 2", after.Message);
+        }
+
+        public class ConfigureContainerStartupServicesFiltersStartup
+        {
+            public void ConfigureContainer(MyContainer services)
+            {
+                services.Services.TryAddSingleton(new ServiceBefore { Message = "Configure container" });
+                services.Services.TryAddSingleton(new ServiceAfter { Message = "Configure container" });
+            }
+
+            public void Configure(IApplicationBuilder builder)
+            {
+            }
+        }
+
+        public class ConfigureServicesAndConfigureContainerStartup
+        {
+            public void ConfigureServices(IServiceCollection services)
+            {
+                services.TryAddSingleton(new ServiceBefore { Message = "Configure services" });
+                services.TryAddSingleton(new ServiceAfter { Message = "Configure services" });
+            }
+
+            public void ConfigureContainer(MyContainer services)
+            {
+                services.Services.TryAddSingleton(new ServiceBefore { Message = "Configure container" });
+                services.Services.TryAddSingleton(new ServiceAfter { Message = "Configure container" });
+            }
+
+            public void Configure(IApplicationBuilder builder)
+            {
+            }
+        }
+
+        public class TestConfigureContainerFilter : IStartupConfigureContainerFilter<MyContainer>
+        {
+            public TestConfigureContainerFilter(object additionalData, bool overrideAfterService)
+            {
+                AdditionalData = additionalData;
+                OverrideAfterService = overrideAfterService;
+            }
+
+            public object AdditionalData { get; }
+            public bool OverrideAfterService { get; }
+
+            public Action<MyContainer> ConfigureContainer(Action<MyContainer> next)
+            {
+                return services =>
+                {
+                    services.Services.TryAddSingleton(new ServiceBefore { Message = $"ConfigureContainerFilter Before {AdditionalData}" });
+
+                    next(services);
+
+                    // Ensures we can always override.
+                    if (OverrideAfterService)
+                    {
+                        services.Services.AddSingleton(new ServiceAfter { Message = $"ConfigureContainerFilter After {AdditionalData}" });
+                    }
+                    else
+                    {
+                        services.Services.TryAddSingleton(new ServiceAfter { Message = $"ConfigureContainerFilter After {AdditionalData}" });
+                    }
+                };
+            }
+        }
+
+        public class IServiceProviderReturningStartupServicesFiltersStartup
+        {
+            public IServiceProvider ConfigureServices(IServiceCollection services)
+            {
+                services.TryAddSingleton(new ServiceBefore { Message = "Configure services" });
+                services.TryAddSingleton(new ServiceAfter { Message = "Configure services" });
+
+                return services.BuildServiceProvider();
+            }
+
+            public void Configure(IApplicationBuilder builder)
+            {
+            }
+        }
+
+        public class TestStartupServicesFilter : IStartupConfigureServicesFilter
+        {
+            public TestStartupServicesFilter(object additionalData, bool overrideAfterService)
+            {
+                AdditionalData = additionalData;
+                OverrideAfterService = overrideAfterService;
+            }
+
+            public object AdditionalData { get; }
+            public bool OverrideAfterService { get; }
+
+            public Action<IServiceCollection> ConfigureServices(Action<IServiceCollection> next)
+            {
+                return services =>
+                {
+                    services.TryAddSingleton(new ServiceBefore { Message = $"StartupServicesFilter Before {AdditionalData}" });
+
+                    next(services);
+
+                    // Ensures we can always override.
+                    if (OverrideAfterService)
+                    {
+                        services.AddSingleton(new ServiceAfter { Message = $"StartupServicesFilter After {AdditionalData}" });
+                    }
+                    else
+                    {
+                        services.TryAddSingleton(new ServiceAfter { Message = $"StartupServicesFilter After {AdditionalData}" });
+                    }
+                };
+            }
+        }
+
+        public class VoidReturningStartupServicesFiltersStartup
+        {
+            public void ConfigureServices(IServiceCollection services)
+            {
+                services.TryAddSingleton(new ServiceBefore { Message = "Configure services" });
+                services.TryAddSingleton(new ServiceAfter { Message = "Configure services" });
+            }
+
+            public void Configure(IApplicationBuilder builder)
+            {
+            }
+        }
+
+
+        public class ServiceBefore
+        {
+            public string Message { get; set; }
+        }
+
+        public class ServiceAfter
+        {
+            public string Message { get; set; }
+        }
+
+        [Fact]
+        public void StartupClassMayHaveHostingServicesInjected()
+        {
+            var callbackStartup = new FakeStartupCallback();
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
+            serviceCollection.AddSingleton<IFakeStartupCallback>(callbackStartup);
+            var services = serviceCollection.BuildServiceProvider();
+
+            var type = StartupLoader.FindStartupType("Microsoft.AspNetCore.Hosting.Tests", "WithServices");
+            var startup = StartupLoader.LoadMethods(services, type, "WithServices");
+
+            var app = new ApplicationBuilder(services);
+            app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection);
+            startup.ConfigureDelegate(app);
+
+            Assert.Equal(2, callbackStartup.MethodsCalled);
+        }
+
+        [Fact]
+        public void StartupClassMayHaveScopedServicesInjected()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<IServiceCollection>>(new DefaultServiceProviderFactory(new ServiceProviderOptions
+            {
+                ValidateScopes = true
+            }));
+
+            serviceCollection.AddScoped<DisposableService>();
+            var services = serviceCollection.BuildServiceProvider();
+
+            var type = StartupLoader.FindStartupType("Microsoft.AspNetCore.Hosting.Tests", "WithScopedServices");
+            var startup = StartupLoader.LoadMethods(services, type, "WithScopedServices");
+            Assert.NotNull(startup.StartupInstance);
+
+            var app = new ApplicationBuilder(services);
+            app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection);
+            startup.ConfigureDelegate(app);
+
+            var instance = (StartupWithScopedServices)startup.StartupInstance;
+            Assert.NotNull(instance.DisposableService);
+            Assert.True(instance.DisposableService.Disposed);
+        }
+
+        [Theory]
+        [InlineData(null)]
+        [InlineData("Dev")]
+        [InlineData("Retail")]
+        [InlineData("Static")]
+        [InlineData("StaticProvider")]
+        [InlineData("Provider")]
+        [InlineData("ProviderArgs")]
+        [InlineData("BaseClass")]
+        public void StartupClassAddsConfigureServicesToApplicationServices(string environment)
+        {
+            var services = new ServiceCollection()
+                .AddSingleton<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>()
+                .BuildServiceProvider();
+            var type = StartupLoader.FindStartupType("Microsoft.AspNetCore.Hosting.Tests", environment);
+            var startup = StartupLoader.LoadMethods(services, type, environment);
+
+            var app = new ApplicationBuilder(services);
+            app.ApplicationServices = startup.ConfigureServicesDelegate(new ServiceCollection());
+            startup.ConfigureDelegate(app);
+
+            var options = app.ApplicationServices.GetRequiredService<IOptions<FakeOptions>>().Value;
+            Assert.NotNull(options);
+            Assert.True(options.Configured);
+            Assert.Equal(environment, options.Environment);
+        }
+
+        [Fact]
+        public void StartupWithNoConfigureThrows()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
+            serviceCollection.AddSingleton<IFakeStartupCallback>(new FakeStartupCallback());
+            var services = serviceCollection.BuildServiceProvider();
+            var type = StartupLoader.FindStartupType("Microsoft.AspNetCore.Hosting.Tests", "Boom");
+            var ex = Assert.Throws<InvalidOperationException>(() => StartupLoader.LoadMethods(services, type, "Boom"));
+            Assert.Equal("A public method named 'ConfigureBoom' or 'Configure' could not be found in the 'Microsoft.AspNetCore.Hosting.Fakes.StartupBoom' type.", ex.Message);
+        }
+
+        [Theory]
+        [InlineData("caseinsensitive")]
+        [InlineData("CaseInsensitive")]
+        [InlineData("CASEINSENSITIVE")]
+        [InlineData("CaSEiNSENsitiVE")]
+        public void FindsStartupClassCaseInsensitive(string environment)
+        {
+            var type = StartupLoader.FindStartupType("Microsoft.AspNetCore.Hosting.Tests", environment);
+
+            Assert.Equal("StartupCaseInsensitive", type.Name);
+        }
+
+        [Theory]
+        [InlineData("caseinsensitive")]
+        [InlineData("CaseInsensitive")]
+        [InlineData("CASEINSENSITIVE")]
+        [InlineData("CaSEiNSENsitiVE")]
+        public void StartupClassAddsConfigureServicesToApplicationServicesCaseInsensitive(string environment)
+        {
+            var services = new ServiceCollection()
+                .AddSingleton<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>()
+                .BuildServiceProvider();
+            var type = StartupLoader.FindStartupType("Microsoft.AspNetCore.Hosting.Tests", environment);
+            var startup = StartupLoader.LoadMethods(services, type, environment);
+
+            var app = new ApplicationBuilder(services);
+            app.ApplicationServices = startup.ConfigureServicesDelegate(new ServiceCollection());
+            startup.ConfigureDelegate(app); // By this not throwing, it found "ConfigureCaseInsensitive"
+
+            var options = app.ApplicationServices.GetRequiredService<IOptions<FakeOptions>>().Value;
+            Assert.NotNull(options);
+            Assert.True(options.Configured);
+            Assert.Equal("ConfigureCaseInsensitiveServices", options.Environment);
+        }
+
+        [Fact]
+        public void StartupWithTwoConfiguresThrows()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
+            serviceCollection.AddSingleton<IFakeStartupCallback>(new FakeStartupCallback());
+            var services = serviceCollection.BuildServiceProvider();
+
+            var type = StartupLoader.FindStartupType("Microsoft.AspNetCore.Hosting.Tests", "TwoConfigures");
+
+            var ex = Assert.Throws<InvalidOperationException>(() => StartupLoader.LoadMethods(services, type, "TwoConfigures"));
+            Assert.Equal("Having multiple overloads of method 'Configure' is not supported.", ex.Message);
+        }
+
+        [Fact]
+        public void StartupWithPrivateConfiguresThrows()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
+            serviceCollection.AddSingleton<IFakeStartupCallback>(new FakeStartupCallback());
+            var services = serviceCollection.BuildServiceProvider();
+
+            var diagnosticMessages = new List<string>();
+            var type = StartupLoader.FindStartupType("Microsoft.AspNetCore.Hosting.Tests", "PrivateConfigure");
+
+            var ex = Assert.Throws<InvalidOperationException>(() => StartupLoader.LoadMethods(services, type, "PrivateConfigure"));
+            Assert.Equal("A public method named 'ConfigurePrivateConfigure' or 'Configure' could not be found in the 'Microsoft.AspNetCore.Hosting.Fakes.StartupPrivateConfigure' type.", ex.Message);
+        }
+
+        [Fact]
+        public void StartupWithTwoConfigureServicesThrows()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
+            serviceCollection.AddSingleton<IFakeStartupCallback>(new FakeStartupCallback());
+            var services = serviceCollection.BuildServiceProvider();
+
+            var type = StartupLoader.FindStartupType("Microsoft.AspNetCore.Hosting.Tests", "TwoConfigureServices");
+
+            var ex = Assert.Throws<InvalidOperationException>(() => StartupLoader.LoadMethods(services, type, "TwoConfigureServices"));
+            Assert.Equal("Having multiple overloads of method 'ConfigureServices' is not supported.", ex.Message);
+        }
+
+        [Fact]
+        public void StartupClassCanHandleConfigureServicesThatReturnsNull()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
+            var services = serviceCollection.BuildServiceProvider();
+
+            var type = StartupLoader.FindStartupType("Microsoft.AspNetCore.Hosting.Tests", "WithNullConfigureServices");
+            var startup = StartupLoader.LoadMethods(services, type, "WithNullConfigureServices");
+
+            var app = new ApplicationBuilder(services);
+            app.ApplicationServices = startup.ConfigureServicesDelegate(new ServiceCollection());
+            Assert.NotNull(app.ApplicationServices);
+            startup.ConfigureDelegate(app);
+            Assert.NotNull(app.ApplicationServices);
+        }
+
+        [Fact]
+        public void StartupClassWithConfigureServicesShouldMakeServiceAvailableInConfigure()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
+            var services = serviceCollection.BuildServiceProvider();
+
+            var type = StartupLoader.FindStartupType("Microsoft.AspNetCore.Hosting.Tests", "WithConfigureServices");
+            var startup = StartupLoader.LoadMethods(services, type, "WithConfigureServices");
+
+            var app = new ApplicationBuilder(services);
+            app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection);
+            startup.ConfigureDelegate(app);
+
+            var foo = app.ApplicationServices.GetRequiredService<StartupWithConfigureServices.IFoo>();
+            Assert.True(foo.Invoked);
+        }
+
+        [Fact]
+        public void StartupLoaderCanLoadByType()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
+            var services = serviceCollection.BuildServiceProvider();
+
+            var hostingEnv = new HostingEnvironment();
+            var startup = StartupLoader.LoadMethods(services, typeof(TestStartup), hostingEnv.EnvironmentName);
+
+            var app = new ApplicationBuilder(services);
+            app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection);
+            startup.ConfigureDelegate(app);
+
+            var foo = app.ApplicationServices.GetRequiredService<SimpleService>();
+            Assert.Equal("Configure", foo.Message);
+        }
+
+        [Fact]
+        public void StartupLoaderCanLoadByTypeWithEnvironment()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
+            var services = serviceCollection.BuildServiceProvider();
+
+            var startup = StartupLoader.LoadMethods(services, typeof(TestStartup), "No");
+
+            var app = new ApplicationBuilder(services);
+            app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection);
+
+            var ex = Assert.Throws<TargetInvocationException>(() => startup.ConfigureDelegate(app));
+            Assert.IsAssignableFrom<InvalidOperationException>(ex.InnerException);
+        }
+
+        [Fact]
+        public void CustomProviderFactoryCallsConfigureContainer()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<MyContainer>, MyContainerFactory>();
+            var services = serviceCollection.BuildServiceProvider();
+
+            var startup = StartupLoader.LoadMethods(services, typeof(MyContainerStartup), EnvironmentName.Development);
+
+            var app = new ApplicationBuilder(services);
+            app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection);
+
+            Assert.IsType<MyContainer>(app.ApplicationServices);
+            Assert.True(((MyContainer)app.ApplicationServices).FancyMethodCalled);
+        }
+
+        [Fact]
+        public void CustomServiceProviderFactoryStartupBaseClassCallsConfigureContainer()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<MyContainer>, MyContainerFactory>();
+            var services = serviceCollection.BuildServiceProvider();
+
+            var startup = StartupLoader.LoadMethods(services, typeof(MyContainerStartupBaseClass), EnvironmentName.Development);
+
+            var app = new ApplicationBuilder(services);
+            app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection);
+
+            Assert.IsType<MyContainer>(app.ApplicationServices);
+            Assert.True(((MyContainer)app.ApplicationServices).FancyMethodCalled);
+        }
+
+        [Fact]
+        public void CustomServiceProviderFactoryEnvironmentBasedConfigureContainer()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<MyContainer>, MyContainerFactory>();
+            var services = serviceCollection.BuildServiceProvider();
+
+            var startup = StartupLoader.LoadMethods(services, typeof(MyContainerStartupEnvironmentBased), EnvironmentName.Production);
+
+            var app = new ApplicationBuilder(services);
+            app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection);
+
+            Assert.IsType<MyContainer>(app.ApplicationServices);
+            Assert.Equal(((MyContainer)app.ApplicationServices).Environment, EnvironmentName.Production);
+        }
+
+        [Fact]
+        public void CustomServiceProviderFactoryThrowsIfNotRegisteredWithConfigureContainerMethod()
+        {
+            var serviceCollection = new ServiceCollection();
+            var services = serviceCollection.BuildServiceProvider();
+
+            var startup = StartupLoader.LoadMethods(services, typeof(MyContainerStartup), EnvironmentName.Development);
+
+            Assert.Throws<InvalidOperationException>(() => startup.ConfigureServicesDelegate(serviceCollection));
+        }
+
+        [Fact]
+        public void CustomServiceProviderFactoryThrowsIfNotRegisteredWithConfigureContainerMethodStartupBase()
+        {
+            var serviceCollection = new ServiceCollection();
+            var services = serviceCollection.BuildServiceProvider();
+
+            Assert.Throws<InvalidOperationException>(() => StartupLoader.LoadMethods(services, typeof(MyContainerStartupBaseClass), EnvironmentName.Development));
+        }
+
+        [Fact]
+        public void CustomServiceProviderFactoryFailsWithOverloadsInStartup()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<MyContainer>, MyContainerFactory>();
+            var services = serviceCollection.BuildServiceProvider();
+
+            Assert.Throws<InvalidOperationException>(() => StartupLoader.LoadMethods(services, typeof(MyContainerStartupWithOverloads), EnvironmentName.Development));
+        }
+
+        [Fact]
+        public void BadServiceProviderFactoryFailsThatReturnsNullServiceProviderOverriddenByDefault()
+        {
+            var serviceCollection = new ServiceCollection();
+            serviceCollection.AddSingleton<IServiceProviderFactory<MyContainer>, MyBadContainerFactory>();
+            var services = serviceCollection.BuildServiceProvider();
+
+            var startup = StartupLoader.LoadMethods(services, typeof(MyContainerStartup), EnvironmentName.Development);
+
+            var app = new ApplicationBuilder(services);
+            app.ApplicationServices = startup.ConfigureServicesDelegate(serviceCollection);
+
+            Assert.NotNull(app.ApplicationServices);
+            Assert.IsNotType<MyContainer>(app.ApplicationServices);
+        }
+
+        public class MyContainerStartupWithOverloads
+        {
+            public void ConfigureServices(IServiceCollection services)
+            {
+
+            }
+
+            public void ConfigureContainer(MyContainer container)
+            {
+                container.MyFancyContainerMethod();
+            }
+
+            public void ConfigureContainer(IServiceCollection services)
+            {
+
+            }
+
+            public void Configure(IApplicationBuilder app)
+            {
+
+            }
+        }
+
+        public class MyContainerStartupEnvironmentBased
+        {
+            public void ConfigureServices(IServiceCollection services)
+            {
+
+            }
+
+            public void ConfigureDevelopmentContainer(MyContainer container)
+            {
+                container.Environment = EnvironmentName.Development;
+            }
+
+            public void ConfigureProductionContainer(MyContainer container)
+            {
+                container.Environment = EnvironmentName.Production;
+            }
+
+            public void Configure(IApplicationBuilder app)
+            {
+
+            }
+        }
+
+        public class MyContainerStartup
+        {
+            public void ConfigureServices(IServiceCollection services)
+            {
+
+            }
+
+            public void ConfigureContainer(MyContainer container)
+            {
+                container.MyFancyContainerMethod();
+            }
+
+            public void Configure(IApplicationBuilder app)
+            {
+
+            }
+        }
+
+        public class MyContainerStartupBaseClass : StartupBase<MyContainer>
+        {
+            public MyContainerStartupBaseClass(IServiceProviderFactory<MyContainer> factory) : base(factory)
+            {
+            }
+
+            public override void Configure(IApplicationBuilder app)
+            {
+
+            }
+
+            public override void ConfigureContainer(MyContainer containerBuilder)
+            {
+                containerBuilder.MyFancyContainerMethod();
+            }
+        }
+
+        public class SimpleService
+        {
+            public SimpleService()
+            {
+            }
+
+            public string Message { get; set; }
+        }
+
+        public class TestStartup
+        {
+            public void ConfigureServices(IServiceCollection services)
+            {
+                services.AddSingleton<SimpleService>();
+            }
+
+            public void ConfigureNoServices(IServiceCollection services)
+            {
+            }
+
+            public void Configure(IApplicationBuilder app)
+            {
+                var service = app.ApplicationServices.GetRequiredService<SimpleService>();
+                service.Message = "Configure";
+            }
+
+            public void ConfigureNo(IApplicationBuilder app)
+            {
+                var service = app.ApplicationServices.GetRequiredService<SimpleService>();
+            }
+        }
+
+        public class FakeStartupCallback : IFakeStartupCallback
+        {
+            private readonly IList<object> _configurationMethodCalledList = new List<object>();
+
+            public int MethodsCalled => _configurationMethodCalledList.Count;
+
+            public void ConfigurationMethodCalled(object instance)
+            {
+                _configurationMethodCalledList.Add(instance);
+            }
+        }
+
+        public class DisposableService : IDisposable
+        {
+            public bool Disposed { get; set; }
+
+            public void Dispose()
+            {
+                Disposed = true;
+            }
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/test/WebHostBuilderTests.cs b/src/Hosting/Hosting/test/WebHostBuilderTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c1244e5c8f7088036bbc49a4334c2a9a7b84f787
--- /dev/null
+++ b/src/Hosting/Hosting/test/WebHostBuilderTests.cs
@@ -0,0 +1,1234 @@
+// 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.Linq;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting.Fakes;
+using Microsoft.AspNetCore.Hosting.Internal;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Logging.Testing;
+using Microsoft.Extensions.ObjectPool;
+using Xunit;
+
+[assembly: HostingStartup(typeof(WebHostBuilderTests.TestHostingStartup))]
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    public class WebHostBuilderTests
+    {
+        [Fact]
+        public void Build_honors_UseStartup_with_string()
+        {
+            var builder = CreateWebHostBuilder().UseServer(new TestServer());
+
+            using (var host = (WebHost)builder.UseStartup("MyStartupAssembly").Build())
+            {
+                Assert.Equal("MyStartupAssembly", host.Options.ApplicationName);
+                Assert.Equal("MyStartupAssembly", host.Options.StartupAssembly);
+            }
+        }
+
+        [Fact]
+        public async Task StartupMissing_Fallback()
+        {
+            var builder = CreateWebHostBuilder();
+            var server = new TestServer();
+            using (var host = builder.UseServer(server).UseStartup("MissingStartupAssembly").Build())
+            {
+                await host.StartAsync();
+                await AssertResponseContains(server.RequestDelegate, "MissingStartupAssembly");
+            }
+        }
+
+        [Fact]
+        public async Task StartupStaticCtorThrows_Fallback()
+        {
+            var builder = CreateWebHostBuilder();
+            var server = new TestServer();
+            var host = builder.UseServer(server).UseStartup<StartupStaticCtorThrows>().Build();
+            using (host)
+            {
+                await host.StartAsync();
+                await AssertResponseContains(server.RequestDelegate, "Exception from static constructor");
+            }
+        }
+
+        [Fact]
+        public async Task StartupCtorThrows_Fallback()
+        {
+            var builder = CreateWebHostBuilder();
+            var server = new TestServer();
+            var host = builder.UseServer(server).UseStartup<StartupCtorThrows>().Build();
+            using (host)
+            {
+                await host.StartAsync();
+                await AssertResponseContains(server.RequestDelegate, "Exception from constructor");
+            }
+        }
+
+        [Fact]
+        public async Task StartupCtorThrows_TypeLoadException()
+        {
+            var builder = CreateWebHostBuilder();
+            var server = new TestServer();
+            var host = builder.UseServer(server).UseStartup<StartupThrowTypeLoadException>().Build();
+            using (host)
+            {
+                await host.StartAsync();
+                await AssertResponseContains(server.RequestDelegate, "Message from the LoaderException</div>");
+            }
+        }
+
+        [Fact]
+        public async Task IApplicationLifetimeRegisteredEvenWhenStartupCtorThrows_Fallback()
+        {
+            var builder = CreateWebHostBuilder();
+            var server = new TestServer();
+            var host = builder.UseServer(server).UseStartup<StartupCtorThrows>().Build();
+            using (host)
+            {
+                await host.StartAsync();
+                var services = host.Services.GetServices<IApplicationLifetime>();
+                Assert.NotNull(services);
+                Assert.NotEmpty(services);
+
+                await AssertResponseContains(server.RequestDelegate, "Exception from constructor");
+            }
+        }
+
+        [Fact]
+        public async Task DefaultObjectPoolProvider_IsRegistered()
+        {
+            var server = new TestServer();
+            var host = CreateWebHostBuilder()
+                .UseServer(server)
+                .Configure(app => { })
+                .Build();
+            using (host)
+            {
+                await host.StartAsync();
+                Assert.IsType<DefaultObjectPoolProvider>(host.Services.GetService<ObjectPoolProvider>());
+            }
+        }
+
+        [Fact]
+        public async Task StartupConfigureServicesThrows_Fallback()
+        {
+            var builder = CreateWebHostBuilder();
+            var server = new TestServer();
+            var host = builder.UseServer(server).UseStartup<StartupConfigureServicesThrows>().Build();
+            using (host)
+            {
+                await host.StartAsync();
+                await AssertResponseContains(server.RequestDelegate, "Exception from ConfigureServices");
+            }
+        }
+
+        [Fact]
+        public async Task StartupConfigureThrows_Fallback()
+        {
+            var builder = CreateWebHostBuilder();
+            var server = new TestServer();
+            var host = builder.UseServer(server).UseStartup<StartupConfigureServicesThrows>().Build();
+            using (host)
+            {
+                await host.StartAsync();
+                await AssertResponseContains(server.RequestDelegate, "Exception from Configure");
+            }
+        }
+
+        [Fact]
+        public void DefaultCreatesLoggerFactory()
+        {
+            var hostBuilder = new WebHostBuilder()
+                .UseServer(new TestServer())
+                .UseStartup<StartupNoServices>();
+
+            using (var host = (WebHost)hostBuilder.Build())
+            {
+                Assert.NotNull(host.Services.GetService<ILoggerFactory>());
+            }
+        }
+
+        [Fact]
+        public void ConfigureDefaultServiceProvider()
+        {
+            var hostBuilder = new WebHostBuilder()
+                .UseServer(new TestServer())
+                .ConfigureServices(s =>
+                {
+                    s.AddTransient<ServiceD>();
+                    s.AddScoped<ServiceC>();
+                })
+                .Configure(app =>
+                {
+                    app.ApplicationServices.GetRequiredService<ServiceC>();
+                })
+                .UseDefaultServiceProvider(options =>
+                {
+                    options.ValidateScopes = true;
+                });
+
+            Assert.Throws<InvalidOperationException>(() => hostBuilder.Build().Start());
+        }
+
+        [Fact]
+        public void ConfigureDefaultServiceProviderWithContext()
+        {
+            var configurationCallbackCalled = false;
+            var hostBuilder = new WebHostBuilder()
+                .UseServer(new TestServer())
+                .ConfigureServices(s =>
+                {
+                    s.AddTransient<ServiceD>();
+                    s.AddScoped<ServiceC>();
+                })
+                .Configure(app =>
+                {
+                    app.ApplicationServices.GetRequiredService<ServiceC>();
+                })
+                .UseDefaultServiceProvider((context, options) =>
+                {
+                    Assert.NotNull(context.HostingEnvironment);
+                    Assert.NotNull(context.Configuration);
+                    configurationCallbackCalled = true;
+                    options.ValidateScopes = true;
+                });
+
+            Assert.Throws<InvalidOperationException>(() => hostBuilder.Build().Start());
+            Assert.True(configurationCallbackCalled);
+        }
+
+        [Fact]
+        public void MultipleConfigureLoggingInvokedInOrder()
+        {
+            var callCount = 0; //Verify ordering
+            var hostBuilder = new WebHostBuilder()
+                .ConfigureLogging(loggerFactory =>
+                {
+                    Assert.Equal(0, callCount++);
+                })
+                .ConfigureLogging(loggerFactory =>
+                {
+                    Assert.Equal(1, callCount++);
+                })
+                .UseServer(new TestServer())
+                .UseStartup<StartupNoServices>();
+
+            using (hostBuilder.Build())
+            {
+                Assert.Equal(2, callCount);
+            }
+        }
+
+        [Fact]
+        public async Task MultipleStartupAssembliesSpecifiedOnlyAddAssemblyOnce()
+        {
+            var provider = new TestLoggerProvider();
+            var assemblyName = "RandomName";
+            var data = new Dictionary<string, string>
+            {
+                { WebHostDefaults.ApplicationKey,  assemblyName },
+                { WebHostDefaults.HostingStartupAssembliesKey, assemblyName }
+            };
+            var config = new ConfigurationBuilder().AddInMemoryCollection(data).Build();
+
+            var builder = CreateWebHostBuilder()
+                 .UseConfiguration(config)
+                 .ConfigureLogging((_, factory) =>
+                 {
+                     factory.AddProvider(provider);
+                 })
+                .UseServer(new TestServer());
+
+            // Verify that there was only one exception throw rather than two.
+            using (var host = (WebHost)builder.Build())
+            {
+                await host.StartAsync();
+                var context = provider.Sink.Writes.Where(s => s.EventId.Id == LoggerEventIds.HostingStartupAssemblyException);
+                Assert.NotNull(context);
+                Assert.Single(context);
+            }
+        }
+
+        [Fact]
+        public void HostingContextContainsAppConfigurationDuringConfigureLogging()
+        {
+            var hostBuilder = new WebHostBuilder()
+                 .ConfigureAppConfiguration((context, configBuilder) =>
+                    configBuilder.AddInMemoryCollection(
+                        new KeyValuePair<string, string>[]
+                        {
+                            new KeyValuePair<string, string>("key1", "value1")
+                        }))
+                 .ConfigureLogging((context, factory) =>
+                 {
+                     Assert.Equal("value1", context.Configuration["key1"]);
+                 })
+                 .UseServer(new TestServer())
+                 .UseStartup<StartupNoServices>();
+
+            using (hostBuilder.Build()) { }
+        }
+
+        [Fact]
+        public void HostingContextContainsAppConfigurationDuringConfigureServices()
+        {
+            var hostBuilder = new WebHostBuilder()
+                 .ConfigureAppConfiguration((context, configBuilder) =>
+                    configBuilder.AddInMemoryCollection(
+                        new KeyValuePair<string, string>[]
+                        {
+                            new KeyValuePair<string, string>("key1", "value1")
+                        }))
+                 .ConfigureServices((context, factory) =>
+                 {
+                     Assert.Equal("value1", context.Configuration["key1"]);
+                 })
+                 .UseServer(new TestServer())
+                 .UseStartup<StartupNoServices>();
+
+            using (hostBuilder.Build()) { }
+        }
+
+        [Fact]
+        public void ThereIsAlwaysConfiguration()
+        {
+            var hostBuilder = new WebHostBuilder()
+                .UseServer(new TestServer())
+                .UseStartup<StartupNoServices>();
+
+            using (var host = (WebHost)hostBuilder.Build())
+            {
+                Assert.NotNull(host.Services.GetService<IConfiguration>());
+            }
+        }
+
+        [Fact]
+        public void ConfigureConfigurationSettingsPropagated()
+        {
+            var hostBuilder = new WebHostBuilder()
+                .UseSetting("key1", "value1")
+                .ConfigureAppConfiguration((context, configBuilder) =>
+                {
+                    var config = configBuilder.Build();
+                    Assert.Equal("value1", config["key1"]);
+                })
+                .UseServer(new TestServer())
+                .UseStartup<StartupNoServices>();
+
+            using (hostBuilder.Build()) { }
+        }
+
+        [Fact]
+        public void CanConfigureConfigurationAndRetrieveFromDI()
+        {
+            var hostBuilder = new WebHostBuilder()
+                .ConfigureAppConfiguration((_, configBuilder) =>
+                {
+                    configBuilder
+                        .AddInMemoryCollection(
+                            new KeyValuePair<string, string>[]
+                            {
+                                new KeyValuePair<string, string>("key1", "value1")
+                            })
+                        .AddEnvironmentVariables();
+                })
+                .UseServer(new TestServer())
+                .UseStartup<StartupNoServices>();
+
+            using (var host = (WebHost)hostBuilder.Build())
+            {
+                var config = host.Services.GetService<IConfiguration>();
+                Assert.NotNull(config);
+                Assert.Equal("value1", config["key1"]);
+            }
+        }
+
+        [Fact]
+        public void DoNotCaptureStartupErrorsByDefault()
+        {
+            var hostBuilder = new WebHostBuilder()
+                .UseServer(new TestServer())
+                .UseStartup<StartupBoom>();
+
+            var exception = Assert.Throws<InvalidOperationException>(() => hostBuilder.Build());
+            Assert.Equal("A public method named 'ConfigureProduction' or 'Configure' could not be found in the 'Microsoft.AspNetCore.Hosting.Fakes.StartupBoom' type.", exception.Message);
+        }
+
+        [Fact]
+        public void ServiceProviderDisposedOnBuildException()
+        {
+            var service = new DisposableService();
+            var hostBuilder = new WebHostBuilder()
+                .UseServer(new TestServer())
+                .ConfigureServices(services =>
+                {
+                    // Added as a factory since instances are never disposed by the container
+                    services.AddSingleton(sp => service);
+                })
+                .UseStartup<StartupWithResolvedDisposableThatThrows>();
+
+            Assert.Throws<InvalidOperationException>(() => hostBuilder.Build());
+            Assert.True(service.Disposed);
+        }
+
+        [Fact]
+        public void CaptureStartupErrorsHonored()
+        {
+            var hostBuilder = new WebHostBuilder()
+                .CaptureStartupErrors(false)
+                .UseServer(new TestServer())
+                .UseStartup<StartupBoom>();
+
+            var exception = Assert.Throws<InvalidOperationException>(() => hostBuilder.Build());
+            Assert.Equal("A public method named 'ConfigureProduction' or 'Configure' could not be found in the 'Microsoft.AspNetCore.Hosting.Fakes.StartupBoom' type.", exception.Message);
+        }
+
+        [Fact]
+        public void ConfigureServices_CanBeCalledMultipleTimes()
+        {
+            var callCount = 0; // Verify ordering
+            var hostBuilder = new WebHostBuilder()
+                .UseServer(new TestServer())
+                .ConfigureServices(services =>
+                {
+                    Assert.Equal(0, callCount++);
+                    services.AddTransient<ServiceA>();
+                })
+                .ConfigureServices(services =>
+                {
+                    Assert.Equal(1, callCount++);
+                    services.AddTransient<ServiceB>();
+                })
+                .Configure(app => { });
+
+            using (var host = hostBuilder.Build())
+            {
+                Assert.Equal(2, callCount);
+
+                Assert.NotNull(host.Services.GetRequiredService<ServiceA>());
+                Assert.NotNull(host.Services.GetRequiredService<ServiceB>());
+            }
+        }
+
+        [Fact]
+        public void CodeBasedSettingsCodeBasedOverride()
+        {
+            var hostBuilder = new WebHostBuilder()
+                .UseSetting(WebHostDefaults.EnvironmentKey, "EnvA")
+                .UseSetting(WebHostDefaults.EnvironmentKey, "EnvB")
+                .UseServer(new TestServer())
+                .UseStartup<StartupNoServices>();
+
+            using (var host = (WebHost)hostBuilder.Build())
+            {
+                Assert.Equal("EnvB", host.Options.Environment);
+            }
+        }
+
+        [Fact]
+        public void CodeBasedSettingsConfigBasedOverride()
+        {
+            var settings = new Dictionary<string, string>
+            {
+                { WebHostDefaults.EnvironmentKey, "EnvB" }
+            };
+
+            var config = new ConfigurationBuilder()
+                .AddInMemoryCollection(settings)
+                .Build();
+
+            var hostBuilder = new WebHostBuilder()
+                .UseSetting(WebHostDefaults.EnvironmentKey, "EnvA")
+                .UseConfiguration(config)
+                .UseServer(new TestServer())
+                .UseStartup<StartupNoServices>();
+
+            using (var host = (WebHost)hostBuilder.Build())
+            {
+                Assert.Equal("EnvB", host.Options.Environment);
+            }
+        }
+
+        [Fact]
+        public void ConfigBasedSettingsCodeBasedOverride()
+        {
+            var settings = new Dictionary<string, string>
+            {
+                { WebHostDefaults.EnvironmentKey, "EnvA" }
+            };
+
+            var config = new ConfigurationBuilder()
+                .AddInMemoryCollection(settings)
+                .Build();
+
+            var hostBuilder = new WebHostBuilder()
+                .UseConfiguration(config)
+                .UseSetting(WebHostDefaults.EnvironmentKey, "EnvB")
+                .UseServer(new TestServer())
+                .UseStartup<StartupNoServices>();
+
+            using (var host = (WebHost)hostBuilder.Build())
+            {
+                Assert.Equal("EnvB", host.Options.Environment);
+            }
+        }
+
+        [Fact]
+        public void ConfigBasedSettingsConfigBasedOverride()
+        {
+            var settings = new Dictionary<string, string>
+            {
+                { WebHostDefaults.EnvironmentKey, "EnvA" }
+            };
+
+            var config = new ConfigurationBuilder()
+                .AddInMemoryCollection(settings)
+                .Build();
+
+            var overrideSettings = new Dictionary<string, string>
+            {
+                { WebHostDefaults.EnvironmentKey, "EnvB" }
+            };
+
+            var overrideConfig = new ConfigurationBuilder()
+                .AddInMemoryCollection(overrideSettings)
+                .Build();
+
+            var hostBuilder = new WebHostBuilder()
+                .UseConfiguration(config)
+                .UseConfiguration(overrideConfig)
+                .UseServer(new TestServer())
+                .UseStartup<StartupNoServices>();
+
+            using (var host = (WebHost)hostBuilder.Build())
+            {
+                Assert.Equal("EnvB", host.Options.Environment);
+            }
+        }
+
+        [Fact]
+        public void UseEnvironmentIsNotOverriden()
+        {
+            var vals = new Dictionary<string, string>
+            {
+                { "ENV", "Dev" },
+            };
+            var builder = new ConfigurationBuilder()
+                .AddInMemoryCollection(vals);
+            var config = builder.Build();
+
+            var expected = "MY_TEST_ENVIRONMENT";
+
+
+            using (var host = new WebHostBuilder()
+                .UseConfiguration(config)
+                .UseEnvironment(expected)
+                .UseServer(new TestServer())
+                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
+                .Build())
+            {
+                Assert.Equal(expected, host.Services.GetService<IHostingEnvironment>().EnvironmentName);
+                Assert.Equal(expected, host.Services.GetService<Extensions.Hosting.IHostingEnvironment>().EnvironmentName);
+            }
+        }
+
+        [Fact]
+        public void BuildAndDispose()
+        {
+            var vals = new Dictionary<string, string>
+            {
+                { "ENV", "Dev" },
+            };
+            var builder = new ConfigurationBuilder()
+                .AddInMemoryCollection(vals);
+            var config = builder.Build();
+
+            var expected = "MY_TEST_ENVIRONMENT";
+            using (var host = new WebHostBuilder()
+                .UseConfiguration(config)
+                .UseEnvironment(expected)
+                .UseServer(new TestServer())
+                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
+                .Build()) { }
+        }
+
+        [Fact]
+        public void UseBasePathConfiguresBasePath()
+        {
+            var vals = new Dictionary<string, string>
+            {
+                { "ENV", "Dev" },
+            };
+            var builder = new ConfigurationBuilder()
+                .AddInMemoryCollection(vals);
+            var config = builder.Build();
+
+            using (var host = new WebHostBuilder()
+                .UseConfiguration(config)
+                .UseContentRoot("/")
+                .UseServer(new TestServer())
+                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
+                .Build())
+            {
+                Assert.Equal("/", host.Services.GetService<IHostingEnvironment>().ContentRootPath);
+                Assert.Equal("/", host.Services.GetService<Extensions.Hosting.IHostingEnvironment>().ContentRootPath);
+            }
+        }
+
+        [Fact]
+        public void RelativeContentRootIsResolved()
+        {
+            using (var host = new WebHostBuilder()
+                .UseContentRoot("testroot")
+                .UseServer(new TestServer())
+                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
+                .Build())
+            {
+                var basePath = host.Services.GetRequiredService<IHostingEnvironment>().ContentRootPath;
+                var basePath2 = host.Services.GetService<Extensions.Hosting.IHostingEnvironment>().ContentRootPath;
+
+                Assert.True(Path.IsPathRooted(basePath));
+                Assert.EndsWith(Path.DirectorySeparatorChar + "testroot", basePath);
+
+                Assert.True(Path.IsPathRooted(basePath2));
+                Assert.EndsWith(Path.DirectorySeparatorChar + "testroot", basePath2);
+            }
+        }
+
+        [Fact]
+        public void DefaultContentRootIsApplicationBasePath()
+        {
+            using (var host = new WebHostBuilder()
+                .UseServer(new TestServer())
+                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
+                .Build())
+            {
+                var appBase = AppContext.BaseDirectory;
+                Assert.Equal(appBase, host.Services.GetService<IHostingEnvironment>().ContentRootPath);
+                Assert.Equal(appBase, host.Services.GetService<Extensions.Hosting.IHostingEnvironment>().ContentRootPath);
+            }
+        }
+
+        [Fact]
+        public void DefaultWebHostBuilderWithNoStartupThrows()
+        {
+            var host = new WebHostBuilder()
+                .UseServer(new TestServer());
+
+            var ex = Assert.Throws<InvalidOperationException>(() => host.Build());
+
+            Assert.Contains("No startup configured.", ex.Message);
+        }
+
+        [Fact]
+        public void DefaultApplicationNameWithUseStartupOfString()
+        {
+            var builder = new ConfigurationBuilder();
+            using (var host = new WebHostBuilder()
+                .UseServer(new TestServer())
+                .UseStartup(typeof(Startup).Assembly.GetName().Name)
+                .Build())
+            {
+                var hostingEnv = host.Services.GetService<IHostingEnvironment>();
+                var hostingEnv2 = host.Services.GetService<Extensions.Hosting.IHostingEnvironment>();
+                Assert.Equal(typeof(Startup).Assembly.GetName().Name, hostingEnv.ApplicationName);
+                Assert.Equal(typeof(Startup).Assembly.GetName().Name, hostingEnv2.ApplicationName);
+            }
+        }
+
+        [Fact]
+        public void DefaultApplicationNameWithUseStartupOfT()
+        {
+            var builder = new ConfigurationBuilder();
+            using (var host = new WebHostBuilder()
+                .UseServer(new TestServer())
+                .UseStartup<StartupNoServices>()
+                .Build())
+            {
+                var hostingEnv = host.Services.GetService<IHostingEnvironment>();
+                var hostingEnv2 = host.Services.GetService<Extensions.Hosting.IHostingEnvironment>();
+                Assert.Equal(typeof(StartupNoServices).Assembly.GetName().Name, hostingEnv.ApplicationName);
+                Assert.Equal(typeof(StartupNoServices).Assembly.GetName().Name, hostingEnv2.ApplicationName);
+            }
+        }
+
+        [Fact]
+        public void DefaultApplicationNameWithUseStartupOfType()
+        {
+            var builder = new ConfigurationBuilder();
+            var host = new WebHostBuilder()
+                .UseServer(new TestServer())
+                .UseStartup(typeof(StartupNoServices))
+                .Build();
+
+            var hostingEnv = host.Services.GetService<IHostingEnvironment>();
+            Assert.Equal(typeof(StartupNoServices).Assembly.GetName().Name, hostingEnv.ApplicationName);
+        }
+
+        [Fact]
+        public void DefaultApplicationNameWithConfigure()
+        {
+            var builder = new ConfigurationBuilder();
+            using (var host = new WebHostBuilder()
+                .UseServer(new TestServer())
+                .Configure(app => { })
+                .Build())
+            {
+                var hostingEnv = host.Services.GetService<IHostingEnvironment>();
+
+                // Should be the assembly containing this test, because that's where the delegate comes from
+                Assert.Equal(typeof(WebHostBuilderTests).Assembly.GetName().Name, hostingEnv.ApplicationName);
+            }
+        }
+
+        [Fact]
+        public void Configure_SupportsNonStaticMethodDelegate()
+        {
+            using (var host = new WebHostBuilder()
+                .UseServer(new TestServer())
+                .Configure(app => { })
+                .Build())
+            {
+                var hostingEnv = host.Services.GetService<IHostingEnvironment>();
+                Assert.Equal("Microsoft.AspNetCore.Hosting.Tests", hostingEnv.ApplicationName);
+            }
+        }
+
+        [Fact]
+        public void Configure_SupportsStaticMethodDelegate()
+        {
+            using (var host = new WebHostBuilder()
+                .UseServer(new TestServer())
+                .Configure(StaticConfigureMethod)
+                .Build())
+            {
+                var hostingEnv = host.Services.GetService<IHostingEnvironment>();
+                Assert.Equal("Microsoft.AspNetCore.Hosting.Tests", hostingEnv.ApplicationName);
+            }
+        }
+
+        [Fact]
+        public void Build_DoesNotAllowBuildingMuiltipleTimes()
+        {
+            var builder = CreateWebHostBuilder();
+            var server = new TestServer();
+            using (builder.UseServer(server)
+                .UseStartup<StartupNoServices>()
+                .Build())
+            {
+                var ex = Assert.Throws<InvalidOperationException>(() => builder.Build());
+                Assert.Equal("WebHostBuilder allows creation only of a single instance of WebHost", ex.Message);
+            }
+        }
+
+        [Fact]
+        public void Build_DoesNotOverrideILoggerFactorySetByConfigureServices()
+        {
+            var factory = new DisposableLoggerFactory();
+            var builder = CreateWebHostBuilder();
+            var server = new TestServer();
+
+            using (var host = builder.UseServer(server)
+                .ConfigureServices(collection => collection.AddSingleton<ILoggerFactory>(factory))
+                .UseStartup<StartupWithILoggerFactory>()
+                .Build())
+            {
+                var factoryFromHost = host.Services.GetService<ILoggerFactory>();
+                Assert.Equal(factory, factoryFromHost);
+            }
+        }
+
+        [Fact]
+        public void Build_RunsHostingStartupAssembliesIfSpecified()
+        {
+            var builder = CreateWebHostBuilder()
+                .CaptureStartupErrors(false)
+                .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(TestStartupAssembly1.TestHostingStartup1).GetTypeInfo().Assembly.FullName)
+                .Configure(app => { })
+                .UseServer(new TestServer());
+
+            using (var host = builder.Build())
+            {
+                Assert.Equal("1", builder.GetSetting("testhostingstartup1"));
+            }
+        }
+
+        [Fact]
+        public void Build_RunsHostingStartupRunsPrimaryAssemblyFirst()
+        {
+            var builder = CreateWebHostBuilder()
+                .CaptureStartupErrors(false)
+                .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(TestStartupAssembly1.TestHostingStartup1).GetTypeInfo().Assembly.FullName)
+                .Configure(app => { })
+                .UseServer(new TestServer());
+
+            using (var host = builder.Build())
+            {
+                Assert.Equal("0", builder.GetSetting("testhostingstartup"));
+                Assert.Equal("1", builder.GetSetting("testhostingstartup1"));
+                Assert.Equal("01", builder.GetSetting("testhostingstartup_chain"));
+            }
+        }
+
+        [Fact]
+        public void Build_RunsHostingStartupAssembliesBeforeApplication()
+        {
+            var startup = new StartupVerifyServiceA();
+            var startupAssemblyName = typeof(WebHostBuilderTests).GetTypeInfo().Assembly.GetName().Name;
+
+            var builder = CreateWebHostBuilder()
+                .CaptureStartupErrors(false)
+                .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(WebHostBuilderTests).GetTypeInfo().Assembly.FullName)
+                .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
+                .ConfigureServices(services =>
+                {
+                    services.AddSingleton<IStartup>(startup);
+                })
+                .UseServer(new TestServer());
+
+            using (var host = builder.Build())
+            {
+                host.Start();
+                Assert.NotNull(startup.ServiceADescriptor);
+                Assert.NotNull(startup.ServiceA);
+            }
+        }
+
+
+        [Fact]
+        public async Task ExternalContainerInstanceCanBeUsedForEverything()
+        {
+            var disposables = new List<DisposableService>();
+
+            var containerFactory = new ExternalContainerFactory(services =>
+            {
+                services.AddSingleton(sp =>
+                {
+                    var service = new DisposableService();
+                    disposables.Add(service);
+                    return service;
+                });
+            });
+
+            var host = new WebHostBuilder()
+                .UseStartup<StartupWithExternalServices>()
+                .UseServer(new TestServer())
+                .ConfigureServices(services =>
+                {
+                    services.AddSingleton<IServiceProviderFactory<IServiceCollection>>(containerFactory);
+                })
+                .Build();
+
+            using (host)
+            {
+                await host.StartAsync();
+            }
+
+            // We should create the hosting service provider and the application service provider
+            Assert.Equal(2, containerFactory.ServiceProviders.Count);
+            Assert.Equal(2, disposables.Count);
+
+            Assert.NotEqual(disposables[0], disposables[1]);
+            Assert.True(disposables[0].Disposed);
+            Assert.True(disposables[1].Disposed);
+        }
+
+        [Fact]
+        public void Build_HostingStartupAssemblyCanBeExcluded()
+        {
+            var builder = CreateWebHostBuilder()
+                .CaptureStartupErrors(false)
+                .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, typeof(TestStartupAssembly1.TestHostingStartup1).GetTypeInfo().Assembly.FullName)
+                .UseSetting(WebHostDefaults.HostingStartupExcludeAssembliesKey, typeof(TestStartupAssembly1.TestHostingStartup1).GetTypeInfo().Assembly.FullName)
+                .Configure(app => { })
+                .UseServer(new TestServer());
+
+            using (var host = builder.Build())
+            {
+                Assert.Null(builder.GetSetting("testhostingstartup1"));
+                Assert.Equal("0", builder.GetSetting("testhostingstartup_chain"));
+            }
+        }
+
+        [Fact]
+        public void Build_ConfigureLoggingInHostingStartupWorks()
+        {
+            var builder = CreateWebHostBuilder()
+                .CaptureStartupErrors(false)
+                .Configure(app =>
+                {
+                    var loggerFactory = app.ApplicationServices.GetService<ILoggerFactory>();
+                    var logger = loggerFactory.CreateLogger(nameof(WebHostBuilderTests));
+                    logger.LogInformation("From startup");
+                })
+                .UseServer(new TestServer());
+
+            using (var host = (WebHost)builder.Build())
+            {
+                host.Start();
+                var sink = host.Services.GetRequiredService<ITestSink>();
+                Assert.Contains(sink.Writes, w => w.State.ToString() == "From startup");
+            }
+        }
+
+        [Fact]
+        public void Build_ConfigureAppConfigurationInHostingStartupWorks()
+        {
+            var builder = CreateWebHostBuilder()
+                .CaptureStartupErrors(false)
+                .Configure(app => { })
+                .UseServer(new TestServer());
+
+            using (var host = (WebHost)builder.Build())
+            {
+                var configuration = host.Services.GetRequiredService<IConfiguration>();
+                Assert.Equal("value", configuration["testhostingstartup:config"]);
+            }
+        }
+
+        [Fact]
+        public void Build_DoesRunHostingStartupFromPrimaryAssemblyEvenIfNotSpecified()
+        {
+            var builder = CreateWebHostBuilder()
+                .Configure(app => { })
+                .UseServer(new TestServer());
+
+            using (builder.Build())
+            {
+                Assert.Equal("0", builder.GetSetting("testhostingstartup"));
+            }
+        }
+
+        [Fact]
+        public void Build_HostingStartupFromPrimaryAssemblyCanBeDisabled()
+        {
+            var builder = CreateWebHostBuilder()
+                .UseSetting(WebHostDefaults.PreventHostingStartupKey, "true")
+                .Configure(app => { })
+                .UseServer(new TestServer());
+
+            using (builder.Build())
+            {
+                Assert.Null(builder.GetSetting("testhostingstartup"));
+            }
+        }
+
+        [Fact]
+        public void Build_DoesntThrowIfUnloadableAssemblyNameInHostingStartupAssemblies()
+        {
+            var builder = CreateWebHostBuilder()
+                .CaptureStartupErrors(false)
+                .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "SomeBogusName")
+                .Configure(app => { })
+                .UseServer(new TestServer());
+
+            using (builder.Build())
+            {
+                Assert.Equal("0", builder.GetSetting("testhostingstartup"));
+            }
+        }
+
+        [Fact]
+        public async Task Build_DoesNotThrowIfUnloadableAssemblyNameInHostingStartupAssembliesAndCaptureStartupErrorsTrue()
+        {
+            var provider = new TestLoggerProvider();
+            var builder = CreateWebHostBuilder()
+                .ConfigureLogging((_, factory) =>
+                {
+                    factory.AddProvider(provider);
+                })
+                .CaptureStartupErrors(true)
+                .UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "SomeBogusName")
+                .Configure(app => { })
+                .UseServer(new TestServer());
+
+            using (var host = builder.Build())
+            {
+                await host.StartAsync();
+                var context = provider.Sink.Writes.FirstOrDefault(s => s.EventId.Id == LoggerEventIds.HostingStartupAssemblyException);
+                Assert.NotNull(context);
+            }
+        }
+
+        [Fact]
+        public void StartupErrorsAreLoggedIfCaptureStartupErrorsIsTrue()
+        {
+            var builder = CreateWebHostBuilder()
+                .CaptureStartupErrors(true)
+                .Configure(app =>
+                {
+                    throw new InvalidOperationException("Startup exception");
+                })
+                .UseServer(new TestServer());
+
+            using (var host = (WebHost)builder.Build())
+            {
+                host.Start();
+                var sink = host.Services.GetRequiredService<ITestSink>();
+                Assert.Contains(sink.Writes, w => w.Exception?.Message == "Startup exception");
+            }
+        }
+
+        [Fact]
+        public void StartupErrorsAreLoggedIfCaptureStartupErrorsIsFalse()
+        {
+            ITestSink testSink = null;
+
+            var builder = CreateWebHostBuilder()
+                .CaptureStartupErrors(false)
+                .Configure(app =>
+                {
+                    testSink = app.ApplicationServices.GetRequiredService<ITestSink>();
+
+                    throw new InvalidOperationException("Startup exception");
+                })
+                .UseServer(new TestServer());
+
+            Assert.Throws<InvalidOperationException>(() => builder.Build().Start());
+
+            Assert.NotNull(testSink);
+            Assert.Contains(testSink.Writes, w => w.Exception?.Message == "Startup exception");
+        }
+
+        [Fact]
+        public void HostingStartupTypeCtorThrowsIfNull()
+        {
+            Assert.Throws<ArgumentNullException>(() => new HostingStartupAttribute(null));
+        }
+
+        [Fact]
+        public void HostingStartupTypeCtorThrowsIfNotIHosting()
+        {
+            Assert.Throws<ArgumentException>(() => new HostingStartupAttribute(typeof(WebHostTests)));
+        }
+
+        [Fact]
+        public void UseShutdownTimeoutConfiguresShutdownTimeout()
+        {
+            var builder = CreateWebHostBuilder()
+                .CaptureStartupErrors(false)
+                .UseShutdownTimeout(TimeSpan.FromSeconds(102))
+                .Configure(app => { })
+                .UseServer(new TestServer());
+
+            using (var host = (WebHost)builder.Build())
+            {
+                Assert.Equal(TimeSpan.FromSeconds(102), host.Options.ShutdownTimeout);
+            }
+        }
+
+        private static void StaticConfigureMethod(IApplicationBuilder app) { }
+
+        private IWebHostBuilder CreateWebHostBuilder()
+        {
+            var vals = new Dictionary<string, string>
+            {
+                { "DetailedErrors", "true" },
+                { "captureStartupErrors", "true" }
+            };
+            var builder = new ConfigurationBuilder()
+                .AddInMemoryCollection(vals);
+            var config = builder.Build();
+            return new WebHostBuilder().UseConfiguration(config);
+        }
+
+        private async Task AssertResponseContains(RequestDelegate app, string expectedText)
+        {
+            var httpContext = new DefaultHttpContext();
+            httpContext.Response.Body = new MemoryStream();
+            await app(httpContext);
+            httpContext.Response.Body.Seek(0, SeekOrigin.Begin);
+            var bodyText = new StreamReader(httpContext.Response.Body).ReadToEnd();
+            Assert.Contains(expectedText, bodyText);
+        }
+
+        private class TestServer : IServer
+        {
+            IFeatureCollection IServer.Features { get; }
+            public RequestDelegate RequestDelegate { get; private set; }
+
+            public void Dispose() { }
+
+            public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
+            {
+                RequestDelegate = async ctx =>
+                {
+                    var httpContext = application.CreateContext(ctx.Features);
+                    try
+                    {
+                        await application.ProcessRequestAsync(httpContext);
+                    }
+                    catch (Exception ex)
+                    {
+                        application.DisposeContext(httpContext, ex);
+                        throw;
+                    }
+                    application.DisposeContext(httpContext, null);
+                };
+
+                return Task.CompletedTask;
+            }
+
+            public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+        }
+
+        internal class ExternalContainerFactory : IServiceProviderFactory<IServiceCollection>
+        {
+            private readonly Action<IServiceCollection> _configureServices;
+            private readonly List<IServiceProvider> _serviceProviders = new List<IServiceProvider>();
+
+            public List<IServiceProvider> ServiceProviders => _serviceProviders;
+
+            public ExternalContainerFactory(Action<IServiceCollection> configureServices)
+            {
+                _configureServices = configureServices;
+            }
+
+            public IServiceCollection CreateBuilder(IServiceCollection services)
+            {
+                _configureServices(services);
+                return services;
+            }
+
+            public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder)
+            {
+                var provider = containerBuilder.BuildServiceProvider();
+                _serviceProviders.Add(provider);
+                return provider;
+            }
+        }
+
+        internal class StartupWithExternalServices
+        {
+            public DisposableService DisposableServiceCtor { get; set; }
+
+            public DisposableService DisposableServiceApp { get; set; }
+
+            public StartupWithExternalServices(DisposableService disposable)
+            {
+                DisposableServiceCtor = disposable;
+            }
+
+            public void ConfigureServices(IServiceCollection services) { }
+
+            public void Configure(IApplicationBuilder app, DisposableService disposable)
+            {
+                DisposableServiceApp = disposable;
+            }
+        }
+
+        internal class StartupVerifyServiceA : IStartup
+        {
+            internal ServiceA ServiceA { get; set; }
+
+            internal ServiceDescriptor ServiceADescriptor { get; set; }
+
+            public IServiceProvider ConfigureServices(IServiceCollection services)
+            {
+                ServiceADescriptor = services.FirstOrDefault(s => s.ServiceType == typeof(ServiceA));
+
+                return services.BuildServiceProvider();
+            }
+
+            public void Configure(IApplicationBuilder app)
+            {
+                ServiceA = app.ApplicationServices.GetService<ServiceA>();
+            }
+        }
+
+        public class DisposableService : IDisposable
+        {
+            public bool Disposed { get; private set; }
+
+            public void Dispose()
+            {
+                Disposed = true;
+            }
+        }
+
+        public class TestHostingStartup : IHostingStartup
+        {
+            public void Configure(IWebHostBuilder builder)
+            {
+                var loggerProvider = new TestLoggerProvider();
+                builder.UseSetting("testhostingstartup", "0")
+                       .UseSetting("testhostingstartup_chain", builder.GetSetting("testhostingstartup_chain") + "0")
+                       .ConfigureServices(services => services.AddSingleton<ServiceA>())
+                       .ConfigureServices(services => services.AddSingleton<ITestSink>(loggerProvider.Sink))
+                       .ConfigureLogging((_, lf) => lf.AddProvider(loggerProvider))
+                       .ConfigureAppConfiguration((context, configurationBuilder) => configurationBuilder.AddInMemoryCollection(
+                           new[]
+                           {
+                               new KeyValuePair<string,string>("testhostingstartup:config", "value")
+                           }));
+            }
+        }
+
+        public class StartupWithResolvedDisposableThatThrows
+        {
+            public StartupWithResolvedDisposableThatThrows(DisposableService service)
+            {
+
+            }
+
+            public void ConfigureServices(IServiceCollection services)
+            {
+                throw new InvalidOperationException();
+            }
+
+            public void Configure(IApplicationBuilder app)
+            {
+
+            }
+        }
+
+        public class TestLoggerProvider : ILoggerProvider
+        {
+            public TestSink Sink { get; set; } = new TestSink();
+
+            public ILogger CreateLogger(string categoryName) => new TestLogger(categoryName, Sink, enabled: true);
+
+            public void Dispose() { }
+        }
+
+        private class ServiceC
+        {
+            public ServiceC(ServiceD serviceD) { }
+        }
+
+        internal class ServiceD { }
+
+        internal class ServiceA { }
+
+        internal class ServiceB { }
+
+        private class DisposableLoggerFactory : ILoggerFactory
+        {
+            public void Dispose()
+            {
+                Disposed = true;
+            }
+
+            public bool Disposed { get; set; }
+
+            public ILogger CreateLogger(string categoryName) => NullLogger.Instance;
+
+            public void AddProvider(ILoggerProvider provider) { }
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/test/WebHostConfigurationsTests.cs b/src/Hosting/Hosting/test/WebHostConfigurationsTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..88a43a4319ac77103383bc3bcf2bcef63ce9ddc7
--- /dev/null
+++ b/src/Hosting/Hosting/test/WebHostConfigurationsTests.cs
@@ -0,0 +1,58 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Hosting.Internal;
+using Microsoft.Extensions.Configuration;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Hosting.Tests
+{
+    public class WebHostConfigurationTests
+    {
+        [Fact]
+        public void ReadsParametersCorrectly()
+        {
+            var parameters = new Dictionary<string, string>()
+            {
+                { WebHostDefaults.WebRootKey, "wwwroot"},
+                { WebHostDefaults.ApplicationKey, "MyProjectReference"},
+                { WebHostDefaults.StartupAssemblyKey, "MyProjectReference" },
+                { WebHostDefaults.EnvironmentKey, EnvironmentName.Development},
+                { WebHostDefaults.DetailedErrorsKey, "true"},
+                { WebHostDefaults.CaptureStartupErrorsKey, "true" },
+                { WebHostDefaults.SuppressStatusMessagesKey, "true" }
+            };
+
+            var config = new WebHostOptions(new ConfigurationBuilder().AddInMemoryCollection(parameters).Build());
+
+            Assert.Equal("wwwroot", config.WebRoot);
+            Assert.Equal("MyProjectReference", config.ApplicationName);
+            Assert.Equal("MyProjectReference", config.StartupAssembly);
+            Assert.Equal(EnvironmentName.Development, config.Environment);
+            Assert.True(config.CaptureStartupErrors);
+            Assert.True(config.DetailedErrors);
+            Assert.True(config.SuppressStatusMessages);
+        }
+
+        [Fact]
+        public void ReadsOldEnvKey()
+        {
+            var parameters = new Dictionary<string, string>() { { "ENVIRONMENT", EnvironmentName.Development } };
+            var config = new WebHostOptions(new ConfigurationBuilder().AddInMemoryCollection(parameters).Build());
+
+            Assert.Equal(EnvironmentName.Development, config.Environment);
+        }
+
+        [Theory]
+        [InlineData("1", true)]
+        [InlineData("0", false)]
+        public void AllowsNumberForDetailedErrors(string value, bool expected)
+        {
+            var parameters = new Dictionary<string, string>() { { "detailedErrors", value } };
+            var config = new WebHostOptions(new ConfigurationBuilder().AddInMemoryCollection(parameters).Build());
+
+            Assert.Equal(expected, config.DetailedErrors);
+        }
+    }
+}
diff --git a/src/Hosting/Hosting/test/WebHostTests.cs b/src/Hosting/Hosting/test/WebHostTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0bc568277c5aaa47a4cea954539a93d2f323510b
--- /dev/null
+++ b/src/Hosting/Hosting/test/WebHostTests.cs
@@ -0,0 +1,1328 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting.Fakes;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Microsoft.Extensions.Primitives;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Hosting
+{
+    public class WebHostTests
+    {
+        [Fact]
+        public async Task WebHostThrowsWithNoServer()
+        {
+            var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => CreateBuilder().Build().StartAsync());
+            Assert.Equal("No service for type 'Microsoft.AspNetCore.Hosting.Server.IServer' has been registered.", ex.Message);
+        }
+
+        [Fact]
+        public void UseStartupThrowsWithNull()
+        {
+            Assert.Throws<ArgumentNullException>(() => CreateBuilder().UseStartup((string)null));
+        }
+
+        [Fact]
+        public async Task NoDefaultAddressesAndDoNotPreferHostingUrlsIfNotConfigured()
+        {
+            using (var host = CreateBuilder().UseFakeServer().Build())
+            {
+                await host.StartAsync();
+                var serverAddressesFeature = host.ServerFeatures.Get<IServerAddressesFeature>();
+                Assert.False(serverAddressesFeature.Addresses.Any());
+                Assert.False(serverAddressesFeature.PreferHostingUrls);
+            }
+        }
+
+        [Fact]
+        public async Task UsesLegacyConfigurationForAddressesAndDoNotPreferHostingUrlsIfNotConfigured()
+        {
+            var data = new Dictionary<string, string>
+            {
+                { "server.urls", "http://localhost:5002" }
+            };
+
+            var config = new ConfigurationBuilder().AddInMemoryCollection(data).Build();
+
+            using (var host = CreateBuilder(config).UseFakeServer().Build())
+            {
+                await host.StartAsync();
+                var serverAddressFeature = host.ServerFeatures.Get<IServerAddressesFeature>();
+                Assert.Equal("http://localhost:5002", serverAddressFeature.Addresses.First());
+                Assert.False(serverAddressFeature.PreferHostingUrls);
+            }
+        }
+
+        [Fact]
+        public void UsesConfigurationForAddressesAndDoNotPreferHostingUrlsIfNotConfigured()
+        {
+            var data = new Dictionary<string, string>
+            {
+                { "urls", "http://localhost:5003" }
+            };
+
+            var config = new ConfigurationBuilder().AddInMemoryCollection(data).Build();
+
+            using (var host = CreateBuilder(config).UseFakeServer().Build())
+            {
+                host.Start();
+                var serverAddressFeature = host.ServerFeatures.Get<IServerAddressesFeature>();
+                Assert.Equal("http://localhost:5003", serverAddressFeature.Addresses.First());
+                Assert.False(serverAddressFeature.PreferHostingUrls);
+            }
+        }
+
+        [Fact]
+        public async Task UsesNewConfigurationOverLegacyConfigForAddressesAndDoNotPreferHostingUrlsIfNotConfigured()
+        {
+            var data = new Dictionary<string, string>
+            {
+                { "server.urls", "http://localhost:5003" },
+                { "urls", "http://localhost:5009" }
+            };
+
+            var config = new ConfigurationBuilder().AddInMemoryCollection(data).Build();
+
+            using (var host = CreateBuilder(config).UseFakeServer().Build())
+            {
+                await host.StartAsync();
+                var serverAddressFeature = host.ServerFeatures.Get<IServerAddressesFeature>();
+                Assert.Equal("http://localhost:5009", serverAddressFeature.Addresses.First());
+                Assert.False(serverAddressFeature.PreferHostingUrls);
+            }
+        }
+
+        [Fact]
+        public void DoNotPreferHostingUrlsWhenNoAddressConfigured()
+        {
+            using (var host = CreateBuilder().UseFakeServer().PreferHostingUrls(true).Build())
+            {
+                host.Start();
+                var serverAddressesFeature = host.ServerFeatures.Get<IServerAddressesFeature>();
+                Assert.Empty(serverAddressesFeature.Addresses);
+                Assert.False(serverAddressesFeature.PreferHostingUrls);
+            }
+        }
+
+        [Fact]
+        public async Task PreferHostingUrlsWhenAddressIsConfigured()
+        {
+            var data = new Dictionary<string, string>
+            {
+                { "urls", "http://localhost:5003" }
+            };
+
+            var config = new ConfigurationBuilder().AddInMemoryCollection(data).Build();
+
+            using (var host = CreateBuilder(config).UseFakeServer().PreferHostingUrls(true).Build())
+            {
+                await host.StartAsync();
+                Assert.True(host.ServerFeatures.Get<IServerAddressesFeature>().PreferHostingUrls);
+            }
+        }
+
+        [Fact]
+        public void WebHostCanBeStarted()
+        {
+            using (var host = CreateBuilder()
+                .UseFakeServer()
+                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
+                .Start())
+            {
+                var server = (FakeServer)host.Services.GetRequiredService<IServer>();
+                Assert.NotNull(host);
+                Assert.Equal(1, server.StartInstances.Count);
+                Assert.Equal(0, server.StartInstances[0].DisposeCalls);
+
+                host.Dispose();
+
+                Assert.Equal(1, server.StartInstances[0].DisposeCalls);
+            }
+        }
+
+        [Fact]
+        public async Task WebHostShutsDownWhenTokenTriggers()
+        {
+            using (var host = CreateBuilder()
+                .UseFakeServer()
+                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
+                .Build())
+            {
+                var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
+                var lifetime2 = host.Services.GetRequiredService<Extensions.Hosting.IApplicationLifetime>();
+                var server = (FakeServer)host.Services.GetRequiredService<IServer>();
+
+                var cts = new CancellationTokenSource();
+
+                var runInBackground = host.RunAsync(cts.Token);
+
+                // Wait on the host to be started
+                lifetime.ApplicationStarted.WaitHandle.WaitOne();
+                Assert.True(lifetime2.ApplicationStarted.IsCancellationRequested);
+
+                Assert.Equal(1, server.StartInstances.Count);
+                Assert.Equal(0, server.StartInstances[0].DisposeCalls);
+
+                cts.Cancel();
+
+                // Wait on the host to shutdown
+                lifetime.ApplicationStopped.WaitHandle.WaitOne();
+                Assert.True(lifetime2.ApplicationStopped.IsCancellationRequested);
+
+                // Wait for RunAsync to finish to guarantee Disposal of WebHost
+                await runInBackground;
+
+                Assert.Equal(1, server.StartInstances[0].DisposeCalls);
+            }
+        }
+
+        [Fact]
+        public async Task WebHostStopAsyncUsesDefaultTimeoutIfGivenTokenDoesNotFire()
+        {
+            var data = new Dictionary<string, string>
+            {
+                { WebHostDefaults.ShutdownTimeoutKey, "1" }
+            };
+
+            var config = new ConfigurationBuilder().AddInMemoryCollection(data).Build();
+
+            var server = new Mock<IServer>();
+            server.Setup(s => s.StopAsync(It.IsAny<CancellationToken>()))
+                .Returns<CancellationToken>(token =>
+                {
+                    return Task.Run(() =>
+                    {
+                        token.WaitHandle.WaitOne();
+                    });
+                });
+
+            using (var host = CreateBuilder(config)
+                .ConfigureServices(services =>
+                {
+                    services.AddSingleton(server.Object);
+                })
+                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
+                .Build())
+            {
+                await host.StartAsync();
+
+                var cts = new CancellationTokenSource();
+
+                // Purposefully don't trigger cts
+                var task = host.StopAsync(cts.Token);
+
+                Assert.Equal(task, await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(10))));
+            }
+        }
+
+        [Fact]
+        public async Task WebHostStopAsyncUsesDefaultTimeoutIfNoTokenProvided()
+        {
+            var data = new Dictionary<string, string>
+            {
+                { WebHostDefaults.ShutdownTimeoutKey, "1" }
+            };
+
+            var config = new ConfigurationBuilder().AddInMemoryCollection(data).Build();
+
+            var server = new Mock<IServer>();
+            server.Setup(s => s.StopAsync(It.IsAny<CancellationToken>()))
+                .Returns<CancellationToken>(token =>
+                {
+                    return Task.Run(() =>
+                    {
+                        token.WaitHandle.WaitOne();
+                    });
+                });
+
+            using (var host = CreateBuilder(config)
+                .ConfigureServices(services =>
+                {
+                    services.AddSingleton(server.Object);
+                })
+                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
+                .Build())
+            {
+                await host.StartAsync();
+
+                var task = host.StopAsync();
+
+                Assert.Equal(task, await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(10))));
+            }
+        }
+
+        [Fact]
+        public async Task WebHostStopAsyncCanBeCancelledEarly()
+        {
+            var data = new Dictionary<string, string>
+            {
+                { WebHostDefaults.ShutdownTimeoutKey, "10" }
+            };
+
+            var config = new ConfigurationBuilder().AddInMemoryCollection(data).Build();
+
+            var server = new Mock<IServer>();
+            server.Setup(s => s.StopAsync(It.IsAny<CancellationToken>()))
+                .Returns<CancellationToken>(token =>
+                {
+                    return Task.Run(() =>
+                    {
+                        token.WaitHandle.WaitOne();
+                    });
+                });
+
+            using (var host = CreateBuilder(config)
+                .ConfigureServices(services =>
+                {
+                    services.AddSingleton(server.Object);
+                })
+                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
+                .Build())
+            {
+                await host.StartAsync();
+
+                var cts = new CancellationTokenSource();
+
+                var task = host.StopAsync(cts.Token);
+                cts.Cancel();
+
+                Assert.Equal(task, await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(8))));
+            }
+        }
+
+        [Fact]
+        public void WebHostApplicationLifetimeEventsOrderedCorrectlyDuringShutdown()
+        {
+            using (var host = CreateBuilder()
+                .UseFakeServer()
+                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
+                .Build())
+            {
+                var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
+                var applicationStartedEvent = new ManualResetEventSlim(false);
+                var applicationStoppingEvent = new ManualResetEventSlim(false);
+                var applicationStoppedEvent = new ManualResetEventSlim(false);
+                var applicationStartedCompletedBeforeApplicationStopping = false;
+                var applicationStoppingCompletedBeforeApplicationStopped = false;
+                var applicationStoppedCompletedBeforeRunCompleted = false;
+
+                lifetime.ApplicationStarted.Register(() =>
+                {
+                    applicationStartedEvent.Set();
+                });
+
+                lifetime.ApplicationStopping.Register(() =>
+                {
+                    // Check whether the applicationStartedEvent has been set
+                    applicationStartedCompletedBeforeApplicationStopping = applicationStartedEvent.IsSet;
+
+                    // Simulate work.
+                    Thread.Sleep(1000);
+
+                    applicationStoppingEvent.Set();
+                });
+
+                lifetime.ApplicationStopped.Register(() =>
+                {
+                    // Check whether the applicationStoppingEvent has been set
+                    applicationStoppingCompletedBeforeApplicationStopped = applicationStoppingEvent.IsSet;
+                    applicationStoppedEvent.Set();
+                });
+
+                var runHostAndVerifyApplicationStopped = Task.Run(async () =>
+                {
+                    await host.RunAsync();
+                    // Check whether the applicationStoppingEvent has been set
+                    applicationStoppedCompletedBeforeRunCompleted = applicationStoppedEvent.IsSet;
+                });
+
+                // Wait until application has started to shut down the host
+                Assert.True(applicationStartedEvent.Wait(5000));
+
+                // Trigger host shutdown on a separate thread
+                Task.Run(() => lifetime.StopApplication());
+
+                // Wait for all events and host.Run() to complete
+                Assert.True(runHostAndVerifyApplicationStopped.Wait(5000));
+
+                // Verify Ordering
+                Assert.True(applicationStartedCompletedBeforeApplicationStopping);
+                Assert.True(applicationStoppingCompletedBeforeApplicationStopped);
+                Assert.True(applicationStoppedCompletedBeforeRunCompleted);
+            }
+        }
+
+        [Fact]
+        public async Task WebHostDisposesServiceProvider()
+        {
+            using (var host = CreateBuilder()
+                .UseFakeServer()
+                .ConfigureServices(s =>
+                {
+                    s.AddTransient<IFakeService, FakeService>();
+                    s.AddSingleton<IFakeSingletonService, FakeService>();
+                })
+                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
+                .Build())
+            {
+                await host.StartAsync();
+
+                var singleton = (FakeService)host.Services.GetService<IFakeSingletonService>();
+                var transient = (FakeService)host.Services.GetService<IFakeService>();
+
+                Assert.False(singleton.Disposed);
+                Assert.False(transient.Disposed);
+
+                await host.StopAsync();
+
+                Assert.False(singleton.Disposed);
+                Assert.False(transient.Disposed);
+
+                host.Dispose();
+
+                Assert.True(singleton.Disposed);
+                Assert.True(transient.Disposed);
+            }
+        }
+
+        [Fact]
+        public async Task WebHostNotifiesApplicationStarted()
+        {
+            using (var host = CreateBuilder()
+                .UseFakeServer()
+                .Build())
+            {
+                var applicationLifetime = host.Services.GetService<IApplicationLifetime>();
+                var applicationLifetime2 = host.Services.GetService<Extensions.Hosting.IApplicationLifetime>();
+
+                Assert.False(applicationLifetime.ApplicationStarted.IsCancellationRequested);
+                Assert.False(applicationLifetime2.ApplicationStarted.IsCancellationRequested);
+
+                await host.StartAsync();
+                Assert.True(applicationLifetime.ApplicationStarted.IsCancellationRequested);
+                Assert.True(applicationLifetime2.ApplicationStarted.IsCancellationRequested);
+            }
+        }
+
+        [Fact]
+        public async Task WebHostNotifiesAllIApplicationLifetimeCallbacksEvenIfTheyThrow()
+        {
+            using (var host = CreateBuilder()
+                .UseFakeServer()
+                .Build())
+            {
+                var applicationLifetime = host.Services.GetService<IApplicationLifetime>();
+                var applicationLifetime2 = host.Services.GetService<Extensions.Hosting.IApplicationLifetime>();
+
+                var started = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStarted);
+                var stopping = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStopping);
+                var stopped = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStopped);
+
+                var started2 = RegisterCallbacksThatThrow(applicationLifetime2.ApplicationStarted);
+                var stopping2 = RegisterCallbacksThatThrow(applicationLifetime2.ApplicationStopping);
+                var stopped2 = RegisterCallbacksThatThrow(applicationLifetime2.ApplicationStopped);
+
+                await host.StartAsync();
+                Assert.True(applicationLifetime.ApplicationStarted.IsCancellationRequested);
+                Assert.True(applicationLifetime2.ApplicationStarted.IsCancellationRequested);
+                Assert.True(started.All(s => s));
+                Assert.True(started2.All(s => s));
+                host.Dispose();
+                Assert.True(stopping.All(s => s));
+                Assert.True(stopping2.All(s => s));
+                Assert.True(stopped.All(s => s));
+                Assert.True(stopped2.All(s => s));
+            }
+        }
+
+        [Fact]
+        public async Task WebHostNotifiesAllIApplicationLifetimeEventsCallbacksEvenIfTheyThrow()
+        {
+            bool[] events1 = null;
+            bool[] events2 = null;
+
+            using (var host = CreateBuilder()
+                .UseFakeServer()
+                .ConfigureServices(services =>
+                {
+                    events1 = RegisterCallbacksThatThrow(services);
+                    events2 = RegisterCallbacksThatThrow(services);
+                })
+                .Build())
+            {
+                await host.StartAsync();
+                Assert.True(events1[0]);
+                Assert.True(events2[0]);
+                host.Dispose();
+                Assert.True(events1[1]);
+                Assert.True(events2[1]);
+            }
+        }
+
+        [Fact]
+        public async Task WebHostStopApplicationDoesNotFireStopOnHostedService()
+        {
+            var stoppingCalls = 0;
+            var disposingCalls = 0;
+
+            using (var host = CreateBuilder()
+                .UseFakeServer()
+                .ConfigureServices(services =>
+                {
+                    Action started = () =>
+                    {
+                    };
+
+                    Action stopping = () =>
+                    {
+                        stoppingCalls++;
+                    };
+
+                    Action disposing = () =>
+                    {
+                        disposingCalls++;
+                    };
+
+                    services.AddSingleton<IHostedService>(_ => new DelegateHostedService(started, stopping, disposing));
+                })
+                .Build())
+            {
+                var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
+                lifetime.StopApplication();
+
+                await host.StartAsync();
+
+                Assert.Equal(0, stoppingCalls);
+                Assert.Equal(0, disposingCalls);
+            }
+            Assert.Equal(1, stoppingCalls);
+            Assert.Equal(1, disposingCalls);
+        }
+
+        [Fact]
+        public async Task HostedServiceCanInjectApplicationLifetime()
+        {
+            using (var host = CreateBuilder()
+                   .UseFakeServer()
+                   .ConfigureServices(services =>
+                   {
+                       services.AddSingleton<IHostedService, TestHostedService>();
+                   })
+                   .Build())
+            {
+                var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
+                lifetime.StopApplication();
+
+                await host.StartAsync();
+                var svc = (TestHostedService)host.Services.GetRequiredService<IHostedService>();
+                Assert.True(svc.StartCalled);
+
+                await host.StopAsync();
+                Assert.True(svc.StopCalled);
+                host.Dispose();
+            }
+        }
+
+        [Fact]
+        public async Task HostedServiceStartNotCalledIfWebHostNotStarted()
+        {
+            using (var host = CreateBuilder()
+                   .UseFakeServer()
+                   .ConfigureServices(services =>
+                   {
+                       services.AddHostedService<TestHostedService>();
+                   })
+                   .Build())
+            {
+                var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
+                lifetime.StopApplication();
+
+                var svc = (TestHostedService)host.Services.GetRequiredService<IHostedService>();
+                Assert.False(svc.StartCalled);
+                await host.StopAsync();
+                Assert.False(svc.StopCalled);
+                host.Dispose();
+                Assert.False(svc.StopCalled);
+                Assert.True(svc.DisposeCalled);
+            }
+        }
+
+        [Fact]
+        public async Task WebHostStopApplicationFiresStopOnHostedService()
+        {
+            var stoppingCalls = 0;
+            var startedCalls = 0;
+            var disposingCalls = 0;
+
+            using (var host = CreateBuilder()
+                .UseFakeServer()
+                .ConfigureServices(services =>
+                {
+                    Action started = () =>
+                    {
+                        startedCalls++;
+                    };
+
+                    Action stopping = () =>
+                    {
+                        stoppingCalls++;
+                    };
+
+                    Action disposing = () =>
+                    {
+                        disposingCalls++;
+                    };
+
+                    services.AddSingleton<IHostedService>(_ => new DelegateHostedService(started, stopping, disposing));
+                })
+                .Build())
+            {
+                var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
+
+                Assert.Equal(0, startedCalls);
+
+                await host.StartAsync();
+                Assert.Equal(1, startedCalls);
+                Assert.Equal(0, stoppingCalls);
+                Assert.Equal(0, disposingCalls);
+
+                await host.StopAsync();
+
+                Assert.Equal(1, startedCalls);
+                Assert.Equal(1, stoppingCalls);
+                Assert.Equal(0, disposingCalls);
+
+                host.Dispose();
+
+                Assert.Equal(1, startedCalls);
+                Assert.Equal(1, stoppingCalls);
+                Assert.Equal(1, disposingCalls);
+            }
+        }
+
+        [Fact]
+        public async Task WebHostDisposeApplicationFiresStopOnHostedService()
+        {
+            var stoppingCalls = 0;
+            var startedCalls = 0;
+            var disposingCalls = 0;
+
+            using (var host = CreateBuilder()
+                .UseFakeServer()
+                .ConfigureServices(services =>
+                {
+                    Action started = () =>
+                    {
+                        startedCalls++;
+                    };
+
+                    Action stopping = () =>
+                    {
+                        stoppingCalls++;
+                    };
+
+                    Action disposing = () =>
+                    {
+                        disposingCalls++;
+                    };
+
+                    services.AddSingleton<IHostedService>(_ => new DelegateHostedService(started, stopping, disposing));
+                })
+                .Build())
+            {
+                var lifetime = host.Services.GetRequiredService<IApplicationLifetime>();
+
+                Assert.Equal(0, startedCalls);
+                await host.StartAsync();
+                Assert.Equal(1, startedCalls);
+                Assert.Equal(0, stoppingCalls);
+                Assert.Equal(0, disposingCalls);
+                host.Dispose();
+
+                Assert.Equal(1, stoppingCalls);
+                Assert.Equal(1, disposingCalls);
+            }
+        }
+
+        [Fact]
+        public async Task WebHostNotifiesAllIHostedServicesAndIApplicationLifetimeCallbacksEvenIfTheyThrow()
+        {
+            bool[] events1 = null;
+            bool[] events2 = null;
+
+            using (var host = CreateBuilder()
+                .UseFakeServer()
+                .ConfigureServices(services =>
+                {
+                    events1 = RegisterCallbacksThatThrow(services);
+                    events2 = RegisterCallbacksThatThrow(services);
+                })
+                .Build())
+            {
+                var applicationLifetime = host.Services.GetService<IApplicationLifetime>();
+                var applicationLifetime2 = host.Services.GetService<Extensions.Hosting.IApplicationLifetime>();
+
+                var started = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStarted);
+                var stopping = RegisterCallbacksThatThrow(applicationLifetime.ApplicationStopping);
+
+                var started2 = RegisterCallbacksThatThrow(applicationLifetime2.ApplicationStarted);
+                var stopping2 = RegisterCallbacksThatThrow(applicationLifetime2.ApplicationStopping);
+
+                await host.StartAsync();
+                Assert.True(events1[0]);
+                Assert.True(events2[0]);
+                Assert.True(started.All(s => s));
+                Assert.True(started2.All(s => s));
+                host.Dispose();
+                Assert.True(events1[1]);
+                Assert.True(events2[1]);
+                Assert.True(stopping.All(s => s));
+                Assert.True(stopping2.All(s => s));
+            }
+        }
+
+        [Fact]
+        public async Task WebHostInjectsHostingEnvironment()
+        {
+            using (var host = CreateBuilder()
+                .UseFakeServer()
+                .UseStartup("Microsoft.AspNetCore.Hosting.Tests")
+                .UseEnvironment("WithHostingEnvironment")
+                .Build())
+            {
+                await host.StartAsync();
+                var env = host.Services.GetService<IHostingEnvironment>();
+                var env2 = host.Services.GetService<Extensions.Hosting.IHostingEnvironment>();
+                Assert.Equal("Changed", env.EnvironmentName);
+                Assert.Equal("Changed", env2.EnvironmentName);
+            }
+        }
+
+        [Fact]
+        public void CanReplaceStartupLoader()
+        {
+            var builder = CreateBuilder()
+                .ConfigureServices(services =>
+                {
+                    services.AddTransient<IStartup, TestStartup>();
+                })
+                .UseFakeServer()
+                .UseStartup("Microsoft.AspNetCore.Hosting.Tests");
+
+            Assert.Throws<NotImplementedException>(() => builder.Build());
+        }
+
+        [Fact]
+        public void CanCreateApplicationServicesWithAddedServices()
+        {
+            using (var host = CreateBuilder().UseFakeServer().ConfigureServices(services => services.AddOptions()).Build())
+            {
+                Assert.NotNull(host.Services.GetRequiredService<IOptions<object>>());
+            }
+        }
+
+        [Fact]
+        public void ConfiguresStartupFiltersInCorrectOrder()
+        {
+            // Verify ordering
+            var configureOrder = 0;
+            using (var host = CreateBuilder()
+                .UseFakeServer()
+                .ConfigureServices(services =>
+                {
+                    services.AddTransient<IStartupFilter>(serviceProvider => new TestFilter(
+                        () => Assert.Equal(1, configureOrder++),
+                        () => Assert.Equal(2, configureOrder++),
+                        () => Assert.Equal(5, configureOrder++)));
+                    services.AddTransient<IStartupFilter>(serviceProvider => new TestFilter(
+                        () => Assert.Equal(0, configureOrder++),
+                        () => Assert.Equal(3, configureOrder++),
+                        () => Assert.Equal(4, configureOrder++)));
+                })
+                .Build())
+            {
+                host.Start();
+                Assert.Equal(6, configureOrder);
+            }
+        }
+
+        private class TestFilter : IStartupFilter
+        {
+            private readonly Action _verifyConfigureOrder;
+            private readonly Action _verifyBuildBeforeOrder;
+            private readonly Action _verifyBuildAfterOrder;
+
+            public TestFilter(Action verifyConfigureOrder, Action verifyBuildBeforeOrder, Action verifyBuildAfterOrder)
+            {
+                _verifyConfigureOrder = verifyConfigureOrder;
+                _verifyBuildBeforeOrder = verifyBuildBeforeOrder;
+                _verifyBuildAfterOrder = verifyBuildAfterOrder;
+            }
+
+            public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
+            {
+                _verifyConfigureOrder();
+                return builder =>
+                {
+                    _verifyBuildBeforeOrder();
+                    next(builder);
+                    _verifyBuildAfterOrder();
+                };
+            }
+        }
+
+        [Fact]
+        public void EnvDefaultsToProductionIfNoConfig()
+        {
+            using (var host = CreateBuilder().UseFakeServer().Build())
+            {
+                var env = host.Services.GetService<IHostingEnvironment>();
+                var env2 = host.Services.GetService<Extensions.Hosting.IHostingEnvironment>();
+                Assert.Equal(EnvironmentName.Production, env.EnvironmentName);
+                Assert.Equal(EnvironmentName.Production, env2.EnvironmentName);
+            }
+        }
+
+        [Fact]
+        public void EnvDefaultsToConfigValueIfSpecified()
+        {
+            var vals = new Dictionary<string, string>
+            {
+                { "Environment", EnvironmentName.Staging }
+            };
+
+            var builder = new ConfigurationBuilder()
+                .AddInMemoryCollection(vals);
+            var config = builder.Build();
+
+            using (var host = CreateBuilder(config).UseFakeServer().Build())
+            {
+                var env = host.Services.GetService<IHostingEnvironment>();
+                var env2 = host.Services.GetService<Extensions.Hosting.IHostingEnvironment>();
+                Assert.Equal(EnvironmentName.Staging, env.EnvironmentName);
+                Assert.Equal(EnvironmentName.Staging, env.EnvironmentName);
+            }
+        }
+
+        [Fact]
+        public void WebRootCanBeResolvedFromTheConfig()
+        {
+            var vals = new Dictionary<string, string>
+            {
+                { "webroot", "testroot" }
+            };
+
+            var builder = new ConfigurationBuilder()
+                .AddInMemoryCollection(vals);
+            var config = builder.Build();
+
+            using (var host = CreateBuilder(config).UseFakeServer().Build())
+            {
+                var env = host.Services.GetService<IHostingEnvironment>();
+                Assert.Equal(Path.GetFullPath("testroot"), env.WebRootPath);
+                Assert.True(env.WebRootFileProvider.GetFileInfo("TextFile.txt").Exists);
+            }
+        }
+
+        [Fact]
+        public async Task IsEnvironment_Extension_Is_Case_Insensitive()
+        {
+            using (var host = CreateBuilder().UseFakeServer().Build())
+            {
+                await host.StartAsync();
+                var env = host.Services.GetRequiredService<IHostingEnvironment>();
+                Assert.True(env.IsEnvironment(EnvironmentName.Production));
+                Assert.True(env.IsEnvironment("producTion"));
+            }
+        }
+
+        [Fact]
+        public async Task WebHost_CreatesDefaultRequestIdentifierFeature_IfNotPresent()
+        {
+            // Arrange
+            HttpContext httpContext = null;
+            var requestDelegate = new RequestDelegate(innerHttpContext =>
+                {
+                    httpContext = innerHttpContext;
+                    return Task.FromResult(0);
+                });
+
+            using (var host = CreateHost(requestDelegate))
+            {
+                // Act
+                await host.StartAsync();
+
+                // Assert
+                Assert.NotNull(httpContext);
+                var featuresTraceIdentifier = httpContext.Features.Get<IHttpRequestIdentifierFeature>().TraceIdentifier;
+                Assert.False(string.IsNullOrWhiteSpace(httpContext.TraceIdentifier));
+                Assert.Same(httpContext.TraceIdentifier, featuresTraceIdentifier);
+            }
+        }
+
+        [Fact]
+        public async Task WebHost_DoesNot_CreateDefaultRequestIdentifierFeature_IfPresent()
+        {
+            // Arrange
+            HttpContext httpContext = null;
+            var requestDelegate = new RequestDelegate(innerHttpContext =>
+            {
+                httpContext = innerHttpContext;
+                return Task.FromResult(0);
+            });
+            var requestIdentifierFeature = new StubHttpRequestIdentifierFeature();
+
+            using (var host = CreateHost(requestDelegate))
+            {
+                var server = (FakeServer)host.Services.GetRequiredService<IServer>();
+                server.CreateRequestFeatures = () =>
+                {
+                    var features = FakeServer.NewFeatureCollection();
+                    features.Set<IHttpRequestIdentifierFeature>(requestIdentifierFeature);
+                    return features;
+                };
+                // Act
+                await host.StartAsync();
+
+                // Assert
+                Assert.NotNull(httpContext);
+                Assert.Same(requestIdentifierFeature, httpContext.Features.Get<IHttpRequestIdentifierFeature>());
+            }
+        }
+
+        [Fact]
+        public async Task WebHost_InvokesConfigureMethodsOnlyOnce()
+        {
+            using (var host = CreateBuilder()
+                .UseFakeServer()
+                .UseStartup<CountStartup>()
+                .Build())
+            {
+                await host.StartAsync();
+                var services = host.Services;
+                var services2 = host.Services;
+                Assert.Equal(1, CountStartup.ConfigureCount);
+                Assert.Equal(1, CountStartup.ConfigureServicesCount);
+            }
+        }
+
+        public class CountStartup
+        {
+            public static int ConfigureServicesCount;
+            public static int ConfigureCount;
+
+            public void ConfigureServices(IServiceCollection services)
+            {
+                ConfigureServicesCount++;
+            }
+
+            public void Configure(IApplicationBuilder app)
+            {
+                ConfigureCount++;
+            }
+        }
+
+        [Fact]
+        public void WebHost_ThrowsForBadConfigureServiceSignature()
+        {
+            var builder = CreateBuilder()
+                .UseFakeServer()
+                .UseStartup<BadConfigureServicesStartup>();
+
+            var ex = Assert.Throws<InvalidOperationException>(() => builder.Build());
+            Assert.Contains("ConfigureServices", ex.Message);
+        }
+
+        public class BadConfigureServicesStartup
+        {
+            public void ConfigureServices(IServiceCollection services, int gunk) { }
+            public void Configure(IApplicationBuilder app) { }
+        }
+
+        private IWebHost CreateHost(RequestDelegate requestDelegate)
+        {
+            var builder = CreateBuilder()
+                .UseFakeServer()
+                .ConfigureLogging((_, factory) =>
+                {
+                    factory.AddProvider(new AllMessagesAreNeeded());
+                })
+                .Configure(
+                    appBuilder =>
+                    {
+                        appBuilder.Run(requestDelegate);
+                    });
+            return builder.Build();
+        }
+
+        private IWebHostBuilder CreateBuilder(IConfiguration config = null)
+        {
+            return new WebHostBuilder().UseConfiguration(config ?? new ConfigurationBuilder().Build()).UseStartup("Microsoft.AspNetCore.Hosting.Tests");
+        }
+
+        private static bool[] RegisterCallbacksThatThrow(IServiceCollection services)
+        {
+            bool[] events = new bool[2];
+
+            Action started = () =>
+            {
+                events[0] = true;
+                throw new InvalidOperationException();
+            };
+
+            Action stopping = () =>
+            {
+                events[1] = true;
+                throw new InvalidOperationException();
+            };
+
+            services.AddSingleton<IHostedService>(new DelegateHostedService(started, stopping, () => { }));
+
+            return events;
+        }
+
+        private static bool[] RegisterCallbacksThatThrow(CancellationToken token)
+        {
+            var signals = new bool[3];
+            for (int i = 0; i < signals.Length; i++)
+            {
+                token.Register(state =>
+                {
+                    signals[(int)state] = true;
+                    throw new InvalidOperationException();
+                }, i);
+            }
+
+            return signals;
+        }
+
+        private class TestHostedService : IHostedService, IDisposable
+        {
+            private readonly IApplicationLifetime _lifetime;
+
+            public TestHostedService(IApplicationLifetime lifetime, Extensions.Hosting.IApplicationLifetime lifetime2)
+            {
+                _lifetime = lifetime;
+            }
+
+            public bool StartCalled { get; set; }
+            public bool StopCalled { get; set; }
+            public bool DisposeCalled { get; set; }
+
+            public Task StartAsync(CancellationToken token)
+            {
+                StartCalled = true;
+                return Task.CompletedTask;
+            }
+
+            public Task StopAsync(CancellationToken token)
+            {
+                StopCalled = true;
+                return Task.CompletedTask;
+            }
+
+            public void Dispose()
+            {
+                DisposeCalled = true;
+            }
+        }
+
+        private class DelegateHostedService : IHostedService, IDisposable
+        {
+            private readonly Action _started;
+            private readonly Action _stopping;
+            private readonly Action _disposing;
+
+            public DelegateHostedService(Action started, Action stopping, Action disposing)
+            {
+                _started = started;
+                _stopping = stopping;
+                _disposing = disposing;
+            }
+
+            public Task StartAsync(CancellationToken token)
+            {
+                _started();
+                return Task.CompletedTask;
+            }
+            public Task StopAsync(CancellationToken token)
+            {
+                _stopping();
+                return Task.CompletedTask;
+            }
+
+            public void Dispose() => _disposing();
+        }
+
+        public class StartInstance : IDisposable
+        {
+            public int StopCalls { get; set; }
+
+            public int DisposeCalls { get; set; }
+
+            public void Stop()
+            {
+                StopCalls += 1;
+            }
+
+            public void Dispose()
+            {
+                DisposeCalls += 1;
+            }
+        }
+
+        public class FakeServer : IServer
+        {
+            public FakeServer()
+            {
+                Features = new FeatureCollection();
+                Features.Set<IServerAddressesFeature>(new ServerAddressesFeature());
+            }
+
+            public IList<StartInstance> StartInstances { get; } = new List<StartInstance>();
+
+            public Func<IFeatureCollection> CreateRequestFeatures { get; set; } = NewFeatureCollection;
+
+            public IFeatureCollection Features { get; }
+
+            public static IFeatureCollection NewFeatureCollection()
+            {
+                var stub = new StubFeatures();
+                var features = new FeatureCollection();
+                features.Set<IHttpRequestFeature>(stub);
+                features.Set<IHttpResponseFeature>(stub);
+                return features;
+            }
+
+            public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
+            {
+                var startInstance = new StartInstance();
+                StartInstances.Add(startInstance);
+                var context = application.CreateContext(CreateRequestFeatures());
+                try
+                {
+                    application.ProcessRequestAsync(context);
+                }
+                catch (Exception ex)
+                {
+                    application.DisposeContext(context, ex);
+                    throw;
+                }
+                application.DisposeContext(context, null);
+
+                return Task.CompletedTask;
+            }
+
+            public Task StopAsync(CancellationToken cancellationToken)
+            {
+                if (StartInstances != null)
+                {
+                    foreach (var startInstance in StartInstances)
+                    {
+                        startInstance.Stop();
+                    }
+                }
+
+                return Task.CompletedTask;
+            }
+
+            public void Dispose()
+            {
+                if (StartInstances != null)
+                {
+                    foreach (var startInstance in StartInstances)
+                    {
+                        startInstance.Dispose();
+                    }
+                }
+            }
+        }
+
+        private class TestStartup : IStartup
+        {
+            public void Configure(IApplicationBuilder app)
+            {
+                throw new NotImplementedException();
+            }
+
+            public IServiceProvider ConfigureServices(IServiceCollection services)
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        private class ReadOnlyFeatureCollection : IFeatureCollection
+        {
+            public object this[Type key]
+            {
+                get { return null; }
+                set { throw new NotSupportedException(); }
+            }
+
+            public bool IsReadOnly
+            {
+                get { return true; }
+            }
+
+            public int Revision
+            {
+                get { return 0; }
+            }
+
+            public void Dispose()
+            {
+            }
+
+            public TFeature Get<TFeature>()
+            {
+                return default(TFeature);
+            }
+
+            public IEnumerator<KeyValuePair<Type, object>> GetEnumerator()
+            {
+                yield break;
+            }
+
+            public void Set<TFeature>(TFeature instance)
+            {
+                throw new NotSupportedException();
+            }
+
+            IEnumerator IEnumerable.GetEnumerator()
+            {
+                yield break;
+            }
+        }
+
+        private class AllMessagesAreNeeded : ILoggerProvider, ILogger
+        {
+            public bool IsEnabled(LogLevel logLevel) => true;
+
+            public ILogger CreateLogger(string name) => this;
+
+            public IDisposable BeginScope<TState>(TState state)
+            {
+                var stringified = state.ToString();
+                return this;
+            }
+            public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
+            {
+                var stringified = formatter(state, exception);
+            }
+
+            public void Dispose()
+            {
+            }
+        }
+
+        private class StubFeatures : IHttpRequestFeature, IHttpResponseFeature, IHeaderDictionary
+        {
+            public StubFeatures()
+            {
+                Headers = this;
+                Body = new MemoryStream();
+            }
+
+            public StringValues this[string key]
+            {
+                get { return StringValues.Empty; }
+                set { }
+            }
+
+            public Stream Body { get; set; }
+
+            public long? ContentLength { get; set; }
+
+            public int Count => 0;
+
+            public bool HasStarted { get; set; }
+
+            public IHeaderDictionary Headers { get; set; }
+
+            public bool IsReadOnly => false;
+
+            public ICollection<string> Keys => null;
+
+            public string Method { get; set; }
+
+            public string Path { get; set; }
+
+            public string PathBase { get; set; }
+
+            public string Protocol { get; set; }
+
+            public string QueryString { get; set; }
+
+            public string RawTarget { get; set; }
+
+            public string ReasonPhrase { get; set; }
+
+            public string Scheme { get; set; }
+
+            public int StatusCode { get; set; }
+
+            public ICollection<StringValues> Values => null;
+
+            public void Add(KeyValuePair<string, StringValues> item) { }
+
+            public void Add(string key, StringValues value) { }
+
+            public void Clear() { }
+
+            public bool Contains(KeyValuePair<string, StringValues> item) => false;
+
+            public bool ContainsKey(string key) => false;
+
+            public void CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex) { }
+
+            public IEnumerator<KeyValuePair<string, StringValues>> GetEnumerator() => null;
+
+            public void OnCompleted(Func<object, Task> callback, object state) { }
+
+            public void OnStarting(Func<object, Task> callback, object state) { }
+
+            public bool Remove(KeyValuePair<string, StringValues> item) => false;
+
+            public bool Remove(string key) => false;
+
+            public bool TryGetValue(string key, out StringValues value)
+            {
+                value = StringValues.Empty;
+                return false;
+            }
+
+            IEnumerator IEnumerable.GetEnumerator() => null;
+        }
+
+        private class StubHttpRequestIdentifierFeature : IHttpRequestIdentifierFeature
+        {
+            public string TraceIdentifier { get; set; }
+        }
+    }
+
+    public static class TestServerWebHostExtensions
+    {
+        public static IWebHostBuilder UseFakeServer(this IWebHostBuilder builder)
+        {
+            return builder.ConfigureServices(services => services.AddSingleton<IServer, WebHostTests.FakeServer>());
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/testroot/TextFile.txt b/src/Hosting/Hosting/test/testroot/TextFile.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d5669ad838619245bb3369435133d6bc2f6ae070
--- /dev/null
+++ b/src/Hosting/Hosting/test/testroot/TextFile.txt
@@ -0,0 +1 @@
+A text file.
\ No newline at end of file
diff --git a/src/Hosting/Hosting/test/testroot/wwwroot/README b/src/Hosting/Hosting/test/testroot/wwwroot/README
new file mode 100644
index 0000000000000000000000000000000000000000..d3415c9f700e3ec63b9cfe1b78c90039167bc764
--- /dev/null
+++ b/src/Hosting/Hosting/test/testroot/wwwroot/README
@@ -0,0 +1 @@
+This file is here to keep directories needed by tests. Do not remove it.
\ No newline at end of file
diff --git a/src/Hosting/Server.Abstractions/src/Features/IServerAddressesFeature.cs b/src/Hosting/Server.Abstractions/src/Features/IServerAddressesFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..24a9a267a5cab61e854b37567e50ac8ba8571087
--- /dev/null
+++ b/src/Hosting/Server.Abstractions/src/Features/IServerAddressesFeature.cs
@@ -0,0 +1,14 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Hosting.Server.Features
+{
+    public interface IServerAddressesFeature
+    {
+        ICollection<string> Addresses { get; }
+
+        bool PreferHostingUrls { get; set; }
+    }
+}
diff --git a/src/Hosting/Server.Abstractions/src/IHttpApplication.cs b/src/Hosting/Server.Abstractions/src/IHttpApplication.cs
new file mode 100644
index 0000000000000000000000000000000000000000..cc9d173da630b6b331e84a5cbf919b254fd6e156
--- /dev/null
+++ b/src/Hosting/Server.Abstractions/src/IHttpApplication.cs
@@ -0,0 +1,36 @@
+// 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.Features;
+
+namespace Microsoft.AspNetCore.Hosting.Server
+{
+    /// <summary>
+    /// Represents an application.
+    /// </summary>
+    /// <typeparam name="TContext">The context associated with the application.</typeparam>
+    public interface IHttpApplication<TContext>
+    {
+        /// <summary>
+        /// Create a TContext given a collection of HTTP features.
+        /// </summary>
+        /// <param name="contextFeatures">A collection of HTTP features to be used for creating the TContext.</param>
+        /// <returns>The created TContext.</returns>
+        TContext CreateContext(IFeatureCollection contextFeatures);
+
+        /// <summary>
+        /// Asynchronously processes an TContext.
+        /// </summary>
+        /// <param name="context">The TContext that the operation will process.</param>
+        Task ProcessRequestAsync(TContext context);
+
+        /// <summary>
+        /// Dispose a given TContext.
+        /// </summary>
+        /// <param name="context">The TContext to be disposed.</param>
+        /// <param name="exception">The Exception thrown when processing did not complete successfully, otherwise null.</param>
+        void DisposeContext(TContext context, Exception exception);
+    }
+}
diff --git a/src/Hosting/Server.Abstractions/src/IServer.cs b/src/Hosting/Server.Abstractions/src/IServer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b04e5a0d47d299f28111e87b1af4c2f7cdd29a03
--- /dev/null
+++ b/src/Hosting/Server.Abstractions/src/IServer.cs
@@ -0,0 +1,35 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Hosting.Server
+{
+    /// <summary>
+    /// Represents a server.
+    /// </summary>
+    public interface IServer : IDisposable
+    {
+        /// <summary>
+        /// A collection of HTTP features of the server.
+        /// </summary>
+        IFeatureCollection Features { get; }
+
+        /// <summary>
+        /// Start the server with an application.
+        /// </summary>
+        /// <param name="application">An instance of <see cref="IHttpApplication{TContext}"/>.</param>
+        /// <typeparam name="TContext">The context associated with the application.</typeparam>
+        /// <param name="cancellationToken">Indicates if the server startup should be aborted.</param>
+        Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Stop processing requests and shut down the server, gracefully if possible.
+        /// </summary>
+        /// <param name="cancellationToken">Indicates if the graceful shutdown should be aborted.</param>
+        Task StopAsync(CancellationToken cancellationToken);
+    }
+}
diff --git a/src/Hosting/Server.Abstractions/src/Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj b/src/Hosting/Server.Abstractions/src/Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..13896a3b2d9a5c9dfbf3ee24f85cf35354eb9b6b
--- /dev/null
+++ b/src/Hosting/Server.Abstractions/src/Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj
@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>ASP.NET Core hosting server abstractions for web applications.</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <NoWarn>$(NoWarn);CS1591</NoWarn>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore;hosting</PackageTags>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Http.Features" />
+    <Reference Include="Microsoft.Extensions.Configuration.Abstractions" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Hosting/Server.Abstractions/src/baseline.netcore.json b/src/Hosting/Server.Abstractions/src/baseline.netcore.json
new file mode 100644
index 0000000000000000000000000000000000000000..30460913bd74cb6a8d1f9d73a6754bf6d40fef57
--- /dev/null
+++ b/src/Hosting/Server.Abstractions/src/baseline.netcore.json
@@ -0,0 +1,150 @@
+{
+  "AssemblyIdentity": "Microsoft.AspNetCore.Hosting.Server.Abstractions, Version=2.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+  "Types": [
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Server.IHttpApplication<T0>",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "CreateContext",
+          "Parameters": [
+            {
+              "Name": "contextFeatures",
+              "Type": "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+            }
+          ],
+          "ReturnType": "T0",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ProcessRequestAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "T0"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "DisposeContext",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "T0"
+            },
+            {
+              "Name": "exception",
+              "Type": "System.Exception"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": [
+        {
+          "ParameterName": "TContext",
+          "ParameterPosition": 0,
+          "BaseTypeOrInterfaces": []
+        }
+      ]
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Server.IServer",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [
+        "System.IDisposable"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Features",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "StartAsync<T0>",
+          "Parameters": [
+            {
+              "Name": "application",
+              "Type": "Microsoft.AspNetCore.Hosting.Server.IHttpApplication<T0>"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": [
+            {
+              "ParameterName": "TContext",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Method",
+          "Name": "StopAsync",
+          "Parameters": [
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.Server.Features.IServerAddressesFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Addresses",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.ICollection<System.String>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_PreferHostingUrls",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_PreferHostingUrls",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/ApplicationType.cs b/src/Hosting/Server.IntegrationTesting/src/Common/ApplicationType.cs
new file mode 100644
index 0000000000000000000000000000000000000000..02d8715984c465c27eb0c11c3708a7065b34fcd9
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/ApplicationType.cs
@@ -0,0 +1,11 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    public enum ApplicationType
+    {
+        Portable,
+        Standalone
+    }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/DeploymentParameters.cs b/src/Hosting/Server.IntegrationTesting/src/Common/DeploymentParameters.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3d824741c37b9d1048ec15ba04867b2fda4c254e
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/DeploymentParameters.cs
@@ -0,0 +1,145 @@
+// 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.Reflection;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    /// <summary>
+    /// Parameters to control application deployment.
+    /// </summary>
+    public class DeploymentParameters
+    {
+        /// <summary>
+        /// Creates an instance of <see cref="DeploymentParameters"/>.
+        /// </summary>
+        /// <param name="applicationPath">Source code location of the target location to be deployed.</param>
+        /// <param name="serverType">Where to be deployed on.</param>
+        /// <param name="runtimeFlavor">Flavor of the clr to run against.</param>
+        /// <param name="runtimeArchitecture">Architecture of the runtime to be used.</param>
+        public DeploymentParameters(
+            string applicationPath,
+            ServerType serverType,
+            RuntimeFlavor runtimeFlavor,
+            RuntimeArchitecture runtimeArchitecture)
+        {
+            if (string.IsNullOrEmpty(applicationPath))
+            {
+                throw new ArgumentException("Value cannot be null.", nameof(applicationPath));
+            }
+
+            if (!Directory.Exists(applicationPath))
+            {
+                throw new DirectoryNotFoundException(string.Format("Application path {0} does not exist.", applicationPath));
+            }
+
+            if (runtimeArchitecture == RuntimeArchitecture.x86 && runtimeFlavor == RuntimeFlavor.CoreClr)
+            {
+                throw new NotSupportedException("32 bit deployment is not yet supported for CoreCLR. Don't remove the tests, just disable them for now.");
+            }
+
+            ApplicationPath = applicationPath;
+            ApplicationName = new DirectoryInfo(ApplicationPath).Name;
+            ServerType = serverType;
+            RuntimeFlavor = runtimeFlavor;
+            EnvironmentVariables["ASPNETCORE_DETAILEDERRORS"] = "true";
+
+            var configAttribute = Assembly.GetCallingAssembly().GetCustomAttribute<AssemblyConfigurationAttribute>();
+            if (configAttribute != null && !string.IsNullOrEmpty(configAttribute.Configuration))
+            {
+                Configuration = configAttribute.Configuration;
+            }
+        }
+
+        public ServerType ServerType { get; }
+
+        public RuntimeFlavor RuntimeFlavor { get; }
+
+        public RuntimeArchitecture RuntimeArchitecture { get; } = RuntimeArchitecture.x64;
+
+        /// <summary>
+        /// Suggested base url for the deployed application. The final deployed url could be
+        /// different than this. Use <see cref="DeploymentResult.ApplicationBaseUri"/> for the
+        /// deployed url.
+        /// </summary>
+        public string ApplicationBaseUriHint { get; set; }
+
+        public string EnvironmentName { get; set; }
+
+        public string ServerConfigTemplateContent { get; set; }
+
+        public string ServerConfigLocation { get; set; }
+
+        public string SiteName { get; set; }
+
+        public string ApplicationPath { get; }
+
+        /// <summary>
+        /// Gets or sets the name of the application. This is used to execute the application when deployed.
+        /// Defaults to the file name of <see cref="ApplicationPath"/>.
+        /// </summary>
+        public string ApplicationName { get; set; }
+
+        public string TargetFramework { get; set; }
+
+        /// <summary>
+        /// Configuration under which to build (ex: Release or Debug)
+        /// </summary>
+        public string Configuration { get; set; } = "Debug";
+
+        /// <summary>
+        /// Space separated command line arguments to be passed to dotnet-publish
+        /// </summary>
+        public string AdditionalPublishParameters { get; set; }
+
+        /// <summary>
+        /// Publish restores by default, this property opts out by default.
+        /// </summary>
+        public bool RestoreOnPublish { get; set; }
+
+        /// <summary>
+        /// To publish the application before deployment.
+        /// </summary>
+        public bool PublishApplicationBeforeDeployment { get; set; }
+
+        public bool PreservePublishedApplicationForDebugging { get; set; } = false;
+
+        public bool StatusMessagesEnabled { get; set; } = true;
+
+        public ApplicationType ApplicationType { get; set; }
+
+        public string PublishedApplicationRootPath { get; set; }
+
+        public HostingModel HostingModel { get; set; }
+
+        /// <summary>
+        /// Environment variables to be set before starting the host.
+        /// Not applicable for IIS Scenarios.
+        /// </summary>
+        public IDictionary<string, string> EnvironmentVariables { get; } = new Dictionary<string, string>();
+
+        /// <summary>
+        /// Environment variables used when invoking dotnet publish.
+        /// </summary>
+        public IDictionary<string, string> PublishEnvironmentVariables { get; } = new Dictionary<string, string>();
+
+        /// <summary>
+        /// For any application level cleanup to be invoked after performing host cleanup.
+        /// </summary>
+        public Action<DeploymentParameters> UserAdditionalCleanup { get; set; }
+
+        public override string ToString()
+        {
+            return string.Format(
+                    "[Variation] :: ServerType={0}, Runtime={1}, Arch={2}, BaseUrlHint={3}, Publish={4}",
+                    ServerType,
+                    RuntimeFlavor,
+                    RuntimeArchitecture,
+                    ApplicationBaseUriHint,
+                    PublishApplicationBeforeDeployment);
+        }
+    }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/DeploymentResult.cs b/src/Hosting/Server.IntegrationTesting/src/Common/DeploymentResult.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6aed1f4d64aaa92f1ed506579c55ed19dcd126cc
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/DeploymentResult.cs
@@ -0,0 +1,69 @@
+// 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.Http;
+using System.Threading;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    /// <summary>
+    /// Result of a deployment.
+    /// </summary>
+    public class DeploymentResult
+    {
+        private readonly ILoggerFactory _loggerFactory;
+
+        /// <summary>
+        /// Base Uri of the deployment application.
+        /// </summary>
+        public string ApplicationBaseUri { get; }
+
+        /// <summary>
+        /// The folder where the application is hosted. This path can be different from the
+        /// original application source location if published before deployment.
+        /// </summary>
+        public string ContentRoot { get; }
+
+        /// <summary>
+        /// Original deployment parameters used for this deployment.
+        /// </summary>
+        public DeploymentParameters DeploymentParameters { get; }
+
+        /// <summary>
+        /// Triggered when the host process dies or pulled down.
+        /// </summary>
+        public CancellationToken HostShutdownToken { get; }
+
+        /// <summary>
+        /// An <see cref="HttpClient"/> with <see cref="LoggingHandler"/> configured and the <see cref="HttpClient.BaseAddress"/> set to the <see cref="ApplicationBaseUri"/>
+        /// </summary>
+        public HttpClient HttpClient { get; }
+
+        public DeploymentResult(ILoggerFactory loggerFactory, DeploymentParameters deploymentParameters, string applicationBaseUri)
+            : this(loggerFactory, deploymentParameters: deploymentParameters, applicationBaseUri: applicationBaseUri, contentRoot: string.Empty, hostShutdownToken: CancellationToken.None)
+        { }
+
+        public DeploymentResult(ILoggerFactory loggerFactory, DeploymentParameters deploymentParameters, string applicationBaseUri, string contentRoot, CancellationToken hostShutdownToken)
+        {
+            _loggerFactory = loggerFactory;
+
+            ApplicationBaseUri = applicationBaseUri;
+            ContentRoot = contentRoot;
+            DeploymentParameters = deploymentParameters;
+            HostShutdownToken = hostShutdownToken;
+
+            HttpClient = CreateHttpClient(new HttpClientHandler());
+        }
+
+        /// <summary>
+        /// Create an <see cref="HttpClient"/> with <see cref="LoggingHandler"/> configured and the <see cref="HttpClient.BaseAddress"/> set to the <see cref="ApplicationBaseUri"/>,
+        /// but using the provided <see cref="HttpMessageHandler"/> and the underlying handler.
+        /// </summary>
+        /// <param name="baseHandler"></param>
+        /// <returns></returns>
+        public HttpClient CreateHttpClient(HttpMessageHandler baseHandler) =>
+            new HttpClient(new LoggingHandler(_loggerFactory, baseHandler)) { BaseAddress = new Uri(ApplicationBaseUri) };
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/HostingModel.cs b/src/Hosting/Server.IntegrationTesting/src/Common/HostingModel.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1eb1aea8c0017b2e0f0d4a7d4f9364dfe671d381
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/HostingModel.cs
@@ -0,0 +1,11 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    public enum HostingModel
+    {
+        OutOfProcess,
+        InProcess
+    }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/LoggingHandler.cs b/src/Hosting/Server.IntegrationTesting/src/Common/LoggingHandler.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a1dc7e24dbd38bcfede738985c392bc3c81808ee
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/LoggingHandler.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    internal class LoggingHandler : DelegatingHandler
+    {
+        private ILogger _logger;
+
+        public LoggingHandler(ILoggerFactory loggerFactory, HttpMessageHandler innerHandler) : base(innerHandler)
+        {
+            _logger = loggerFactory.CreateLogger<HttpClient>();
+        }
+
+        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+        {
+            _logger.LogDebug("Sending {method} {url}", request.Method, request.RequestUri);
+            try
+            {
+                var response = await base.SendAsync(request, cancellationToken);
+                _logger.LogDebug("Received {statusCode} {reasonPhrase} {url}", response.StatusCode, response.ReasonPhrase, request.RequestUri);
+                return response;
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError(0, ex, "Exception while sending '{method} {url}' : {exception}", request.Method, request.RequestUri, ex);
+                throw;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/ProcessLoggingExtensions.cs b/src/Hosting/Server.IntegrationTesting/src/Common/ProcessLoggingExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8d7d20bc1e97e12bd5bf9c5a43955e2b53bd3d59
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/ProcessLoggingExtensions.cs
@@ -0,0 +1,34 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.Extensions.Logging;
+
+namespace System.Diagnostics
+{
+    public static class ProcessLoggingExtensions
+    {
+        public static void StartAndCaptureOutAndErrToLogger(this Process process, string prefix, ILogger logger)
+        {
+            process.EnableRaisingEvents = true;
+            process.OutputDataReceived += (_, dataArgs) =>
+            {
+                if (!string.IsNullOrEmpty(dataArgs.Data))
+                {
+                    logger.LogInformation($"{prefix} stdout: {{line}}", dataArgs.Data);
+                }
+            };
+
+            process.ErrorDataReceived += (_, dataArgs) =>
+            {
+                if (!string.IsNullOrEmpty(dataArgs.Data))
+                {
+                    logger.LogWarning($"{prefix} stderr: {{line}}", dataArgs.Data);
+                }
+            };
+
+            process.Start();
+            process.BeginErrorReadLine();
+            process.BeginOutputReadLine();
+        }
+    }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/RetryHelper.cs b/src/Hosting/Server.IntegrationTesting/src/Common/RetryHelper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..75ac9f6f41041fae7054270ac7bfd708ed25c269
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/RetryHelper.cs
@@ -0,0 +1,94 @@
+// 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;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    public class RetryHelper
+    {
+        /// <summary>
+        /// Retries every 1 sec for 60 times by default.
+        /// </summary>
+        /// <param name="retryBlock"></param>
+        /// <param name="logger"></param>
+        /// <param name="cancellationToken"></param>
+        /// <param name="retryCount"></param>
+        public static async Task<HttpResponseMessage> RetryRequest(
+            Func<Task<HttpResponseMessage>> retryBlock,
+            ILogger logger,
+            CancellationToken cancellationToken = default,
+            int retryCount = 60)
+        {
+            for (var retry = 0; retry < retryCount; retry++)
+            {
+                if (cancellationToken.IsCancellationRequested)
+                {
+                    logger.LogInformation("Failed to connect, retry canceled.");
+                    throw new OperationCanceledException("Failed to connect, retry canceled.", cancellationToken);
+                }
+
+                try
+                {
+                    logger.LogWarning("Retry count {retryCount}..", retry + 1);
+                    var response = await retryBlock().ConfigureAwait(false);
+
+                    if (response.StatusCode == HttpStatusCode.ServiceUnavailable)
+                    {
+                        // Automatically retry on 503. May be application is still booting.
+                        logger.LogWarning("Retrying a service unavailable error.");
+                        continue;
+                    }
+
+                    return response; // Went through successfully
+                }
+                catch (Exception exception)
+                {
+                    if (retry == retryCount - 1)
+                    {
+                        logger.LogError(0, exception, "Failed to connect, retry limit exceeded.");
+                        throw;
+                    }
+                    else
+                    {
+                        if (exception is HttpRequestException || exception is WebException)
+                        {
+                            logger.LogWarning("Failed to complete the request : {0}.", exception.Message);
+                            await Task.Delay(1 * 1000); //Wait for a while before retry.
+                        }
+                    }
+                }
+            }
+
+            logger.LogInformation("Failed to connect, retry limit exceeded.");
+            throw new OperationCanceledException("Failed to connect, retry limit exceeded.");
+        }
+
+        public static void RetryOperation(
+            Action retryBlock,
+            Action<Exception> exceptionBlock,
+            int retryCount = 3,
+            int retryDelayMilliseconds = 0)
+        {
+            for (var retry = 0; retry < retryCount; ++retry)
+            {
+                try
+                {
+                    retryBlock();
+                    break;
+                }
+                catch (Exception exception)
+                {
+                    exceptionBlock(exception);
+                }
+
+                Thread.Sleep(retryDelayMilliseconds);
+            }
+        }
+    }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/RuntimeArchitecture.cs b/src/Hosting/Server.IntegrationTesting/src/Common/RuntimeArchitecture.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1e9c868b4bb03bda56ea5db092f387cce29422b3
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/RuntimeArchitecture.cs
@@ -0,0 +1,11 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    public enum RuntimeArchitecture
+    {
+        x64,
+        x86
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/RuntimeFlavor.cs b/src/Hosting/Server.IntegrationTesting/src/Common/RuntimeFlavor.cs
new file mode 100644
index 0000000000000000000000000000000000000000..510c713f59d5fc94853aea2160600aa2a966c997
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/RuntimeFlavor.cs
@@ -0,0 +1,11 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    public enum RuntimeFlavor
+    {
+        Clr,
+        CoreClr
+    }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/ServerType.cs b/src/Hosting/Server.IntegrationTesting/src/Common/ServerType.cs
new file mode 100644
index 0000000000000000000000000000000000000000..060c8ed0cade861b774ca2e9b03ca3e4ad7e7938
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/ServerType.cs
@@ -0,0 +1,14 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    public enum ServerType
+    {
+        IISExpress,
+        IIS,
+        WebListener,
+        Kestrel,
+        Nginx
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Server.IntegrationTesting/src/Common/TestUriHelper.cs b/src/Hosting/Server.IntegrationTesting/src/Common/TestUriHelper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7ebcb30367749d8b5680dd8702bad5dafe847ecd
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Common/TestUriHelper.cs
@@ -0,0 +1,85 @@
+// 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.Sockets;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting.Common
+{
+    public static class TestUriHelper
+    {
+        public static Uri BuildTestUri()
+        {
+            return BuildTestUri(null);
+        }
+
+        public static Uri BuildTestUri(string hint)
+        {
+            // If this method is called directly, there is no way to know the server type or whether status messages
+            // are enabled.  It's safest to assume the server is WebListener (which doesn't support binding to dynamic
+            // port "0") and status messages are not enabled (so the assigned port cannot be scraped from console output).
+            return BuildTestUri(hint, serverType: ServerType.WebListener, statusMessagesEnabled: false);
+        }
+
+        internal static Uri BuildTestUri(string hint, ServerType serverType, bool statusMessagesEnabled)
+        {
+            if (string.IsNullOrEmpty(hint))
+            {
+                if (serverType == ServerType.Kestrel && statusMessagesEnabled)
+                {
+                    // Most functional tests use this codepath and should directly bind to dynamic port "0" and scrape
+                    // the assigned port from the status message, which should be 100% reliable since the port is bound
+                    // once and never released.  Binding to dynamic port "0" on "localhost" (both IPv4 and IPv6) is not
+                    // supported, so the port is only bound on "127.0.0.1" (IPv4).  If a test explicitly requires IPv6,
+                    // it should provide a hint URL with "localhost" (IPv4 and IPv6) or "[::1]" (IPv6-only).
+                    return new UriBuilder("http", "127.0.0.1", 0).Uri;
+                }
+                else
+                {
+                    // If the server type is not Kestrel, or status messages are disabled, there is no status message
+                    // from which to scrape the assigned port, so the less reliable GetNextPort() must be used.  The
+                    // port is bound on "localhost" (both IPv4 and IPv6), since this is supported when using a specific
+                    // (non-zero) port.
+                    return new UriBuilder("http", "localhost", GetNextPort()).Uri;
+                }
+            }
+            else
+            {
+                var uriHint = new Uri(hint);
+                if (uriHint.Port == 0)
+                {
+                    // Only a few tests use this codepath, so it's fine to use the less reliable GetNextPort() for simplicity.
+                    // The tests using this codepath will be reviewed to see if they can be changed to directly bind to dynamic
+                    // port "0" on "127.0.0.1" and scrape the assigned port from the status message (the default codepath).
+                    return new UriBuilder(uriHint) { Port = GetNextPort() }.Uri;
+                }
+                else
+                {
+                    // If the hint contains a specific port, return it unchanged.
+                    return uriHint;
+                }
+            }
+        }
+
+        // Copied from https://github.com/aspnet/KestrelHttpServer/blob/47f1db20e063c2da75d9d89653fad4eafe24446c/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/AddressRegistrationTests.cs#L508
+        //
+        // This method is an attempt to safely get a free port from the OS.  Most of the time,
+        // when binding to dynamic port "0" the OS increments the assigned port, so it's safe
+        // to re-use the assigned port in another process.  However, occasionally the OS will reuse
+        // a recently assigned port instead of incrementing, which causes flaky tests with AddressInUse
+        // exceptions.  This method should only be used when the application itself cannot use
+        // dynamic port "0" (e.g. IISExpress).  Most functional tests using raw Kestrel
+        // (with status messages enabled) should directly bind to dynamic port "0" and scrape 
+        // the assigned port from the status message, which should be 100% reliable since the port
+        // is bound once and never released.
+        public static int GetNextPort()
+        {
+            using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+            {
+                socket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
+                return ((IPEndPoint)socket.LocalEndPoint).Port;
+            }
+        }
+    }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/ApplicationDeployer.cs b/src/Hosting/Server.IntegrationTesting/src/Deployers/ApplicationDeployer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3c81f3290200f33bb95995c6cc05891ea555403d
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/ApplicationDeployer.cs
@@ -0,0 +1,252 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Internal;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    /// <summary>
+    /// Abstract base class of all deployers with implementation of some of the common helpers.
+    /// </summary>
+    public abstract class ApplicationDeployer : IApplicationDeployer
+    {
+        public static readonly string DotnetCommandName = "dotnet";
+
+        // This is the argument that separates the dotnet arguments for the args being passed to the
+        // app being run when running dotnet run
+        public static readonly string DotnetArgumentSeparator = "--";
+
+        private readonly Stopwatch _stopwatch = new Stopwatch();
+
+        public ApplicationDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
+        {
+            DeploymentParameters = deploymentParameters;
+            LoggerFactory = loggerFactory;
+            Logger = LoggerFactory.CreateLogger(GetType().FullName);
+        }
+
+        protected DeploymentParameters DeploymentParameters { get; }
+
+        protected ILoggerFactory LoggerFactory { get; }
+        protected ILogger Logger { get; }
+
+        public abstract Task<DeploymentResult> DeployAsync();
+
+        protected void DotnetPublish(string publishRoot = null)
+        {
+            using (Logger.BeginScope("dotnet-publish"))
+            {
+                if (string.IsNullOrEmpty(DeploymentParameters.TargetFramework))
+                {
+                    throw new Exception($"A target framework must be specified in the deployment parameters for applications that require publishing before deployment");
+                }
+
+                DeploymentParameters.PublishedApplicationRootPath = publishRoot ?? Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
+
+                var parameters = $"publish "
+                    + $" --output \"{DeploymentParameters.PublishedApplicationRootPath}\""
+                    + $" --framework {DeploymentParameters.TargetFramework}"
+                    + $" --configuration {DeploymentParameters.Configuration}"
+                    + (DeploymentParameters.RestoreOnPublish 
+                        ? string.Empty
+                        : " --no-restore -p:VerifyMatchingImplicitPackageVersion=false");
+                        // Set VerifyMatchingImplicitPackageVersion to disable errors when Microsoft.NETCore.App's version is overridden externally
+                        // This verification doesn't matter if we are skipping restore during tests.
+
+                if (DeploymentParameters.ApplicationType == ApplicationType.Standalone)
+                {
+                    parameters += $" --runtime {GetRuntimeIdentifier()}";
+                }
+
+                parameters += $" {DeploymentParameters.AdditionalPublishParameters}";
+
+                var startInfo = new ProcessStartInfo
+                {
+                    FileName = DotnetCommandName,
+                    Arguments = parameters,
+                    UseShellExecute = false,
+                    CreateNoWindow = true,
+                    RedirectStandardError = true,
+                    RedirectStandardOutput = true,
+                    WorkingDirectory = DeploymentParameters.ApplicationPath,
+                };
+
+                AddEnvironmentVariablesToProcess(startInfo, DeploymentParameters.PublishEnvironmentVariables);
+
+                var hostProcess = new Process() { StartInfo = startInfo };
+
+                Logger.LogInformation($"Executing command {DotnetCommandName} {parameters}");
+
+                hostProcess.StartAndCaptureOutAndErrToLogger("dotnet-publish", Logger);
+
+                hostProcess.WaitForExit();
+
+                if (hostProcess.ExitCode != 0)
+                {
+                    var message = $"{DotnetCommandName} publish exited with exit code : {hostProcess.ExitCode}";
+                    Logger.LogError(message);
+                    throw new Exception(message);
+                }
+
+                Logger.LogInformation($"{DotnetCommandName} publish finished with exit code : {hostProcess.ExitCode}");
+            }
+        }
+
+        protected void CleanPublishedOutput()
+        {
+            using (Logger.BeginScope("CleanPublishedOutput"))
+            {
+                if (DeploymentParameters.PreservePublishedApplicationForDebugging)
+                {
+                    Logger.LogWarning(
+                        "Skipping deleting the locally published folder as property " +
+                        $"'{nameof(DeploymentParameters.PreservePublishedApplicationForDebugging)}' is set to 'true'.");
+                }
+                else
+                {
+                    RetryHelper.RetryOperation(
+                        () => Directory.Delete(DeploymentParameters.PublishedApplicationRootPath, true),
+                        e => Logger.LogWarning($"Failed to delete directory : {e.Message}"),
+                        retryCount: 3,
+                        retryDelayMilliseconds: 100);
+                }
+            }
+        }
+
+        protected void ShutDownIfAnyHostProcess(Process hostProcess)
+        {
+            if (hostProcess != null && !hostProcess.HasExited)
+            {
+                Logger.LogInformation("Attempting to cancel process {0}", hostProcess.Id);
+
+                // Shutdown the host process.
+                hostProcess.KillTree();
+                if (!hostProcess.HasExited)
+                {
+                    Logger.LogWarning("Unable to terminate the host process with process Id '{processId}", hostProcess.Id);
+                }
+                else
+                {
+                    Logger.LogInformation("Successfully terminated host process with process Id '{processId}'", hostProcess.Id);
+                }
+            }
+            else
+            {
+                Logger.LogWarning("Host process already exited or never started successfully.");
+            }
+        }
+
+        protected void AddEnvironmentVariablesToProcess(ProcessStartInfo startInfo, IDictionary<string, string> environmentVariables)
+        {
+            var environment = startInfo.Environment;
+            SetEnvironmentVariable(environment, "ASPNETCORE_ENVIRONMENT", DeploymentParameters.EnvironmentName);
+
+            foreach (var environmentVariable in environmentVariables)
+            {
+                SetEnvironmentVariable(environment, environmentVariable.Key, environmentVariable.Value);
+            }
+        }
+
+        protected void SetEnvironmentVariable(IDictionary<string, string> environment, string name, string value)
+        {
+            if (value == null)
+            {
+                Logger.LogInformation("Removing environment variable {name}", name);
+                environment.Remove(name);
+            }
+            else
+            {
+                Logger.LogInformation("SET {name}={value}", name, value);
+                environment[name] = value;
+            }
+        }
+
+        protected void InvokeUserApplicationCleanup()
+        {
+            using (Logger.BeginScope("UserAdditionalCleanup"))
+            {
+                if (DeploymentParameters.UserAdditionalCleanup != null)
+                {
+                    // User cleanup.
+                    try
+                    {
+                        DeploymentParameters.UserAdditionalCleanup(DeploymentParameters);
+                    }
+                    catch (Exception exception)
+                    {
+                        Logger.LogWarning("User cleanup code failed with exception : {exception}", exception.Message);
+                    }
+                }
+            }
+        }
+
+        protected void TriggerHostShutdown(CancellationTokenSource hostShutdownSource)
+        {
+            Logger.LogInformation("Host process shutting down.");
+            try
+            {
+                hostShutdownSource.Cancel();
+            }
+            catch (Exception)
+            {
+                // Suppress errors.
+            }
+        }
+
+        protected void StartTimer()
+        {
+            Logger.LogInformation($"Deploying {DeploymentParameters.ToString()}");
+            _stopwatch.Start();
+        }
+
+        protected void StopTimer()
+        {
+            _stopwatch.Stop();
+            Logger.LogInformation("[Time]: Total time taken for this test variation '{t}' seconds", _stopwatch.Elapsed.TotalSeconds);
+        }
+
+        public abstract void Dispose();
+
+        private string GetRuntimeIdentifier()
+        {
+            var architecture = GetArchitecture();
+            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+            {
+                return "win7-" + architecture;
+            }
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+            {
+                return "linux-" + architecture;
+            }
+            else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+            {
+                return "osx-" + architecture;
+            }
+            else
+            {
+                throw new InvalidOperationException("Unrecognized operation system platform");
+            }
+        }
+
+        private string GetArchitecture()
+        {
+            switch (RuntimeInformation.OSArchitecture)
+            {
+                case Architecture.X86:
+                    return "x86";
+                case Architecture.X64:
+                    return "x64";
+                default:
+                    throw new NotSupportedException($"Unsupported architecture: {RuntimeInformation.OSArchitecture}");
+            }
+        }
+    }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/ApplicationDeployerFactory.cs b/src/Hosting/Server.IntegrationTesting/src/Deployers/ApplicationDeployerFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..eb66761807a75c5590b3f057b3046bed01d093bd
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/ApplicationDeployerFactory.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;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    /// <summary>
+    /// Factory to create an appropriate deployer based on <see cref="DeploymentParameters"/>.
+    /// </summary>
+    public class ApplicationDeployerFactory
+    {
+        /// <summary>
+        /// Creates a deployer instance based on settings in <see cref="DeploymentParameters"/>.
+        /// </summary>
+        /// <param name="deploymentParameters"></param>
+        /// <param name="loggerFactory"></param>
+        /// <returns></returns>
+        public static IApplicationDeployer Create(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
+        {
+            if (deploymentParameters == null)
+            {
+                throw new ArgumentNullException(nameof(deploymentParameters));
+            }
+
+            if (loggerFactory == null)
+            {
+                throw new ArgumentNullException(nameof(loggerFactory));
+            }
+
+            switch (deploymentParameters.ServerType)
+            {
+                case ServerType.IISExpress:
+                    return new IISExpressDeployer(deploymentParameters, loggerFactory);
+                case ServerType.IIS:
+                    throw new NotSupportedException("The IIS deployer is no longer supported");
+                case ServerType.WebListener:
+                case ServerType.Kestrel:
+                    return new SelfHostDeployer(deploymentParameters, loggerFactory);
+                case ServerType.Nginx:
+                    return new NginxDeployer(deploymentParameters, loggerFactory);
+                default:
+                    throw new NotSupportedException(
+                        string.Format("Found no deployers suitable for server type '{0}' with the current runtime.",
+                        deploymentParameters.ServerType)
+                        );
+            }
+        }
+    }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/IApplicationDeployer.cs b/src/Hosting/Server.IntegrationTesting/src/Deployers/IApplicationDeployer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..400ae978edcf70839eae1ed75786dbb2ca9e1652
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/IApplicationDeployer.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 System;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    /// <summary>
+    /// Common operations on an application deployer.
+    /// </summary>
+    public interface IApplicationDeployer : IDisposable
+    {
+        /// <summary>
+        /// Deploys the application to the target with specified <see cref="DeploymentParameters"/>.
+        /// </summary>
+        /// <returns></returns>
+        Task<DeploymentResult> DeployAsync();
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/IISExpressDeployer.cs b/src/Hosting/Server.IntegrationTesting/src/Deployers/IISExpressDeployer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..bc7aecb70096b3a016398be56c48c6344167ac3e
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/IISExpressDeployer.cs
@@ -0,0 +1,317 @@
+// 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.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
+using Microsoft.AspNetCore.Testing;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    /// <summary>
+    /// Deployment helper for IISExpress.
+    /// </summary>
+    public class IISExpressDeployer : ApplicationDeployer
+    {
+        private const string IISExpressRunningMessage = "IIS Express is running.";
+        private const string FailedToInitializeBindingsMessage = "Failed to initialize site bindings";
+        private const string UnableToStartIISExpressMessage = "Unable to start iisexpress.";
+        private const int MaximumAttempts = 5;
+
+        private static readonly Regex UrlDetectorRegex = new Regex(@"^\s*Successfully registered URL ""(?<url>[^""]+)"" for site.*$");
+
+        private Process _hostProcess;
+
+        public IISExpressDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
+            : base(deploymentParameters, loggerFactory)
+        {
+        }
+
+        public bool IsWin8OrLater
+        {
+            get
+            {
+                var win8Version = new Version(6, 2);
+
+                return (Environment.OSVersion.Version >= win8Version);
+            }
+        }
+
+        public bool Is64BitHost
+        {
+            get
+            {
+                return Environment.Is64BitOperatingSystem;
+            }
+        }
+
+        public override async Task<DeploymentResult> DeployAsync()
+        {
+            using (Logger.BeginScope("Deployment"))
+            {
+                // Start timer
+                StartTimer();
+
+                // For now we always auto-publish. Otherwise we'll have to write our own local web.config for the HttpPlatformHandler
+                DeploymentParameters.PublishApplicationBeforeDeployment = true;
+                if (DeploymentParameters.PublishApplicationBeforeDeployment)
+                {
+                    DotnetPublish();
+                }
+
+                var contentRoot = DeploymentParameters.PublishApplicationBeforeDeployment ? DeploymentParameters.PublishedApplicationRootPath : DeploymentParameters.ApplicationPath;
+
+                var testUri = TestUriHelper.BuildTestUri(DeploymentParameters.ApplicationBaseUriHint);
+
+                // Launch the host process.
+                var (actualUri, hostExitToken) = await StartIISExpressAsync(testUri, contentRoot);
+
+                Logger.LogInformation("Application ready at URL: {appUrl}", actualUri);
+
+                // Right now this works only for urls like http://localhost:5001/. Does not work for http://localhost:5001/subpath.
+                return new DeploymentResult(
+                    LoggerFactory,
+                    DeploymentParameters,
+                    applicationBaseUri: actualUri.ToString(),
+                    contentRoot: contentRoot,
+                    hostShutdownToken: hostExitToken);
+            }
+        }
+
+        private async Task<(Uri url, CancellationToken hostExitToken)> StartIISExpressAsync(Uri uri, string contentRoot)
+        {
+            using (Logger.BeginScope("StartIISExpress"))
+            {
+                var port = uri.Port;
+                if (port == 0)
+                {
+                    port = TestUriHelper.GetNextPort();
+                }
+
+                for (var attempt = 0; attempt < MaximumAttempts; attempt++)
+                {
+                    Logger.LogInformation("Attempting to start IIS Express on port: {port}", port);
+
+                    if (!string.IsNullOrWhiteSpace(DeploymentParameters.ServerConfigTemplateContent))
+                    {
+                        var serverConfig = DeploymentParameters.ServerConfigTemplateContent;
+
+                        // Pass on the applicationhost.config to iis express. With this don't need to pass in the /path /port switches as they are in the applicationHost.config
+                        // We take a copy of the original specified applicationHost.Config to prevent modifying the one in the repo.
+
+                        if (serverConfig.Contains("[ANCMPath]"))
+                        {
+                            // We need to pick the bitness based the OS / IIS Express, not the application.
+                            // We'll eventually add support for choosing which IIS Express bitness to run: https://github.com/aspnet/Hosting/issues/880
+                            var ancmFile = Path.Combine(contentRoot, Is64BitHost ? @"x64\aspnetcore.dll" : @"x86\aspnetcore.dll");
+                            // Bin deployed by Microsoft.AspNetCore.AspNetCoreModule.nupkg
+
+                            if (!File.Exists(Environment.ExpandEnvironmentVariables(ancmFile)))
+                            {
+                                throw new FileNotFoundException("AspNetCoreModule could not be found.", ancmFile);
+                            }
+
+                            Logger.LogDebug("Writing ANCMPath '{ancmPath}' to config", ancmFile);
+                            serverConfig =
+                                serverConfig.Replace("[ANCMPath]", ancmFile);
+                        }
+
+                        Logger.LogDebug("Writing ApplicationPhysicalPath '{applicationPhysicalPath}' to config", contentRoot);
+                        Logger.LogDebug("Writing Port '{port}' to config", port);
+                        serverConfig =
+                            serverConfig
+                                .Replace("[ApplicationPhysicalPath]", contentRoot)
+                                .Replace("[PORT]", port.ToString());
+
+                        DeploymentParameters.ServerConfigLocation = Path.GetTempFileName();
+
+                        if (serverConfig.Contains("[HostingModel]"))
+                        {
+                            var hostingModel = DeploymentParameters.HostingModel.ToString();
+                            serverConfig.Replace("[HostingModel]", hostingModel);
+                            Logger.LogDebug("Writing HostingModel '{hostingModel}' to config", hostingModel);
+                        }
+
+                        Logger.LogDebug("Saving Config to {configPath}", DeploymentParameters.ServerConfigLocation);
+
+                        if (Logger.IsEnabled(LogLevel.Trace))
+                        {
+                            Logger.LogTrace($"Config File Content:{Environment.NewLine}===START CONFIG==={Environment.NewLine}{{configContent}}{Environment.NewLine}===END CONFIG===", serverConfig);
+                        }
+
+                        File.WriteAllText(DeploymentParameters.ServerConfigLocation, serverConfig);
+                    }
+
+                    if (DeploymentParameters.HostingModel == HostingModel.InProcess)
+                    {
+                        ModifyWebConfigToInProcess();
+                    }
+
+                    var parameters = string.IsNullOrWhiteSpace(DeploymentParameters.ServerConfigLocation) ?
+                                    string.Format("/port:{0} /path:\"{1}\" /trace:error", uri.Port, contentRoot) :
+                                    string.Format("/site:{0} /config:{1} /trace:error", DeploymentParameters.SiteName, DeploymentParameters.ServerConfigLocation);
+
+                    var iisExpressPath = GetIISExpressPath();
+
+                    Logger.LogInformation("Executing command : {iisExpress} {parameters}", iisExpressPath, parameters);
+
+                    var startInfo = new ProcessStartInfo
+                    {
+                        FileName = iisExpressPath,
+                        Arguments = parameters,
+                        UseShellExecute = false,
+                        CreateNoWindow = true,
+                        RedirectStandardError = true,
+                        RedirectStandardOutput = true
+                    };
+
+                    AddEnvironmentVariablesToProcess(startInfo, DeploymentParameters.EnvironmentVariables);
+
+                    Uri url = null;
+                    var started = new TaskCompletionSource<bool>();
+
+                    var process = new Process() { StartInfo = startInfo };
+                    process.OutputDataReceived += (sender, dataArgs) =>
+                    {
+                        if (string.Equals(dataArgs.Data, UnableToStartIISExpressMessage))
+                        {
+                            // We completely failed to start and we don't really know why
+                            started.TrySetException(new InvalidOperationException("Failed to start IIS Express"));
+                        }
+                        else if (string.Equals(dataArgs.Data, FailedToInitializeBindingsMessage))
+                        {
+                            started.TrySetResult(false);
+                        }
+                        else if (string.Equals(dataArgs.Data, IISExpressRunningMessage))
+                        {
+                            started.TrySetResult(true);
+                        }
+                        else if (!string.IsNullOrEmpty(dataArgs.Data))
+                        {
+                            var m = UrlDetectorRegex.Match(dataArgs.Data);
+                            if (m.Success)
+                            {
+                                url = new Uri(m.Groups["url"].Value);
+                            }
+                        }
+                    };
+
+                    process.EnableRaisingEvents = true;
+                    var hostExitTokenSource = new CancellationTokenSource();
+                    process.Exited += (sender, e) =>
+                    {
+                        Logger.LogInformation("iisexpress Process {pid} shut down", process.Id);
+
+                        // If TrySetResult was called above, this will just silently fail to set the new state, which is what we want
+                        started.TrySetException(new Exception($"Command exited unexpectedly with exit code: {process.ExitCode}"));
+
+                        TriggerHostShutdown(hostExitTokenSource);
+                    };
+                    process.StartAndCaptureOutAndErrToLogger("iisexpress", Logger);
+                    Logger.LogInformation("iisexpress Process {pid} started", process.Id);
+
+                    if (process.HasExited)
+                    {
+                        Logger.LogError("Host process {processName} {pid} exited with code {exitCode} or failed to start.", startInfo.FileName, process.Id, process.ExitCode);
+                        throw new Exception("Failed to start host");
+                    }
+
+                    // Wait for the app to start
+                    // The timeout here is large, because we don't know how long the test could need
+                    // We cover a lot of error cases above, but I want to make sure we eventually give up and don't hang the build
+                    // just in case we missed one -anurse
+                    if (!await started.Task.TimeoutAfter(TimeSpan.FromMinutes(10)))
+                    {
+                        Logger.LogInformation("iisexpress Process {pid} failed to bind to port {port}, trying again", _hostProcess.Id, port);
+
+                        // Wait for the process to exit and try again
+                        process.WaitForExit(30 * 1000);
+                        await Task.Delay(1000); // Wait a second to make sure the socket is completely cleaned up
+                    }
+                    else
+                    {
+                        _hostProcess = process;
+                        Logger.LogInformation("Started iisexpress successfully. Process Id : {processId}, Port: {port}", _hostProcess.Id, port);
+                        return (url: url, hostExitToken: hostExitTokenSource.Token);
+                    }
+                }
+
+                var message = $"Failed to initialize IIS Express after {MaximumAttempts} attempts to select a port";
+                Logger.LogError(message);
+                throw new TimeoutException(message);
+            }
+        }
+
+        private string GetIISExpressPath()
+        {
+            // Get path to program files
+            var iisExpressPath = Path.Combine(Environment.GetEnvironmentVariable("SystemDrive") + "\\", "Program Files", "IIS Express", "iisexpress.exe");
+
+            if (!File.Exists(iisExpressPath))
+            {
+                throw new Exception("Unable to find IISExpress on the machine: " + iisExpressPath);
+            }
+
+            return iisExpressPath;
+        }
+
+        public override void Dispose()
+        {
+            using (Logger.BeginScope("Dispose"))
+            {
+                ShutDownIfAnyHostProcess(_hostProcess);
+
+                if (!string.IsNullOrWhiteSpace(DeploymentParameters.ServerConfigLocation)
+                    && File.Exists(DeploymentParameters.ServerConfigLocation))
+                {
+                    // Delete the temp applicationHostConfig that we created.
+                    Logger.LogDebug("Deleting applicationHost.config file from {configLocation}", DeploymentParameters.ServerConfigLocation);
+                    try
+                    {
+                        File.Delete(DeploymentParameters.ServerConfigLocation);
+                    }
+                    catch (Exception exception)
+                    {
+                        // Ignore delete failures - just write a log.
+                        Logger.LogWarning("Failed to delete '{config}'. Exception : {exception}", DeploymentParameters.ServerConfigLocation, exception.Message);
+                    }
+                }
+
+                if (DeploymentParameters.PublishApplicationBeforeDeployment)
+                {
+                    CleanPublishedOutput();
+                }
+
+                InvokeUserApplicationCleanup();
+
+                StopTimer();
+            }
+
+            // If by this point, the host process is still running (somehow), throw an error.
+            // A test failure is better than a silent hang and unknown failure later on
+            if (_hostProcess != null && !_hostProcess.HasExited)
+            {
+                throw new Exception($"iisexpress Process {_hostProcess.Id} failed to shutdown");
+            }
+        }
+
+        // Transforms the web.config file to include the hostingModel="inprocess" element
+        // and adds the server type = Microsoft.AspNetServer.IIS such that Kestrel isn't added again in ServerTests
+        private void ModifyWebConfigToInProcess()
+        {
+            var webConfigFile = $"{DeploymentParameters.PublishedApplicationRootPath}/web.config";
+            var config = XDocument.Load(webConfigFile);
+            var element = config.Descendants("aspNetCore").FirstOrDefault();
+            element.SetAttributeValue("hostingModel", "inprocess");
+            config.Save(webConfigFile);
+        }
+    }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/NginxDeployer.cs b/src/Hosting/Server.IntegrationTesting/src/Deployers/NginxDeployer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1bc6c4b766ad90ce3d52cca11a9beca56983f9df
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/NginxDeployer.cs
@@ -0,0 +1,165 @@
+// 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.IO;
+using System.Net.Http;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    /// <summary>
+    /// Deployer for Kestrel on Nginx.
+    /// </summary>
+    public class NginxDeployer : SelfHostDeployer
+    {
+        private string _configFile;
+        private readonly int _waitTime = (int)TimeSpan.FromSeconds(30).TotalMilliseconds;
+
+        public NginxDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
+            : base(deploymentParameters, loggerFactory)
+        {
+        }
+
+        public override async Task<DeploymentResult> DeployAsync()
+        {
+            using (Logger.BeginScope("Deploy"))
+            {
+                _configFile = Path.GetTempFileName();
+                var uri = string.IsNullOrEmpty(DeploymentParameters.ApplicationBaseUriHint) ?
+                    TestUriHelper.BuildTestUri() :
+                    new Uri(DeploymentParameters.ApplicationBaseUriHint);
+
+                var redirectUri = TestUriHelper.BuildTestUri();
+
+                if (DeploymentParameters.PublishApplicationBeforeDeployment)
+                {
+                    DotnetPublish();
+                }
+
+                var (appUri, exitToken) = await StartSelfHostAsync(redirectUri);
+
+                SetupNginx(appUri.ToString(), uri);
+
+                Logger.LogInformation("Application ready at URL: {appUrl}", uri);
+
+                // Wait for App to be loaded since Nginx returns 502 instead of 503 when App isn't loaded
+                // Target actual address to avoid going through Nginx proxy
+                using (var httpClient = new HttpClient())
+                {
+                    var response = await RetryHelper.RetryRequest(() =>
+                    {
+                        return httpClient.GetAsync(redirectUri);
+                    }, Logger, exitToken);
+
+                    if (!response.IsSuccessStatusCode)
+                    {
+                        throw new InvalidOperationException("Deploy failed");
+                    }
+                }
+
+                return new DeploymentResult(
+                    LoggerFactory,
+                    DeploymentParameters,
+                    applicationBaseUri: uri.ToString(),
+                    contentRoot: DeploymentParameters.ApplicationPath,
+                    hostShutdownToken: exitToken);
+            }
+        }
+
+        private void SetupNginx(string redirectUri, Uri originalUri)
+        {
+            using (Logger.BeginScope("SetupNginx"))
+            {
+                // copy nginx.conf template and replace pertinent information
+                var pidFile = Path.Combine(DeploymentParameters.ApplicationPath, $"{Guid.NewGuid()}.nginx.pid");
+                var errorLog = Path.Combine(DeploymentParameters.ApplicationPath, "nginx.error.log");
+                var accessLog = Path.Combine(DeploymentParameters.ApplicationPath, "nginx.access.log");
+                DeploymentParameters.ServerConfigTemplateContent = DeploymentParameters.ServerConfigTemplateContent
+                    .Replace("[user]", Environment.GetEnvironmentVariable("LOGNAME"))
+                    .Replace("[errorlog]", errorLog)
+                    .Replace("[accesslog]", accessLog)
+                    .Replace("[listenPort]", originalUri.Port.ToString())
+                    .Replace("[redirectUri]", redirectUri)
+                    .Replace("[pidFile]", pidFile);
+                Logger.LogDebug("Using PID file: {pidFile}", pidFile);
+                Logger.LogDebug("Using Error Log file: {errorLog}", pidFile);
+                Logger.LogDebug("Using Access Log file: {accessLog}", pidFile);
+                if (Logger.IsEnabled(LogLevel.Trace))
+                {
+                    Logger.LogTrace($"Config File Content:{Environment.NewLine}===START CONFIG==={Environment.NewLine}{{configContent}}{Environment.NewLine}===END CONFIG===", DeploymentParameters.ServerConfigTemplateContent);
+                }
+                File.WriteAllText(_configFile, DeploymentParameters.ServerConfigTemplateContent);
+
+                var startInfo = new ProcessStartInfo
+                {
+                    FileName = "nginx",
+                    Arguments = $"-c {_configFile}",
+                    UseShellExecute = false,
+                    CreateNoWindow = true,
+                    RedirectStandardError = true,
+                    RedirectStandardOutput = true,
+                    // Trying a work around for https://github.com/aspnet/Hosting/issues/140.
+                    RedirectStandardInput = true
+                };
+
+                using (var runNginx = new Process() { StartInfo = startInfo })
+                {
+                    runNginx.StartAndCaptureOutAndErrToLogger("nginx start", Logger);
+                    runNginx.WaitForExit(_waitTime);
+                    if (runNginx.ExitCode != 0)
+                    {
+                        throw new Exception("Failed to start nginx");
+                    }
+
+                    // Read the PID file
+                    if(!File.Exists(pidFile))
+                    {
+                        Logger.LogWarning("Unable to find nginx PID file: {pidFile}", pidFile);
+                    }
+                    else
+                    {
+                        var pid = File.ReadAllText(pidFile);
+                        Logger.LogInformation("nginx process ID {pid} started", pid);
+                    }
+                }
+            }
+        }
+
+        public override void Dispose()
+        {
+            using (Logger.BeginScope("Dispose"))
+            {
+                if (File.Exists(_configFile))
+                {
+                    var startInfo = new ProcessStartInfo
+                    {
+                        FileName = "nginx",
+                        Arguments = $"-s stop -c {_configFile}",
+                        UseShellExecute = false,
+                        CreateNoWindow = true,
+                        RedirectStandardError = true,
+                        RedirectStandardOutput = true,
+                        // Trying a work around for https://github.com/aspnet/Hosting/issues/140.
+                        RedirectStandardInput = true
+                    };
+
+                    using (var runNginx = new Process() { StartInfo = startInfo })
+                    {
+                        runNginx.StartAndCaptureOutAndErrToLogger("nginx stop", Logger);
+                        runNginx.WaitForExit(_waitTime);
+                        Logger.LogInformation("nginx stop command issued");
+                    }
+
+                    Logger.LogDebug("Deleting config file: {configFile}", _configFile);
+                    File.Delete(_configFile);
+                }
+
+                base.Dispose();
+            }
+        }
+    }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/RemotePSSessionHelper.ps1 b/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/RemotePSSessionHelper.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..e5c54d21e810eed78d5cb9327383854abe4bdde8
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/RemotePSSessionHelper.ps1
@@ -0,0 +1,67 @@
+[CmdletBinding()]
+param(
+	[Parameter(Mandatory=$true)]
+	[string]$serverName,
+
+	[Parameter(Mandatory=$true)]
+	[string]$accountName,
+
+	[Parameter(Mandatory=$true)]
+	[string]$accountPassword,
+
+	[Parameter(Mandatory=$true)]
+	[string]$deployedFolderPath,
+
+	[Parameter(Mandatory=$false)]
+	[string]$dotnetRuntimePath = "",
+
+	[Parameter(Mandatory=$true)]
+	[string]$executablePath,
+
+	[Parameter(Mandatory=$false)]
+	[string]$executableParameters,
+
+	[Parameter(Mandatory=$true)]
+	[string]$serverType,
+
+	[Parameter(Mandatory=$true)]
+	[string]$serverAction,
+
+	[Parameter(Mandatory=$true)]
+	[string]$applicationBaseUrl,
+
+	[Parameter(Mandatory=$false)]
+	[string]$environmentVariables
+)
+
+Write-Host "`nExecuting deployment helper script on machine '$serverName'"
+Write-Host "`nStarting a powershell session to machine '$serverName'"
+
+$securePassword = ConvertTo-SecureString $accountPassword -AsPlainText -Force
+$credentials= New-Object System.Management.Automation.PSCredential ($accountName, $securePassword)
+$psSession = New-PSSession -ComputerName $serverName -credential $credentials
+
+$remoteResult="0"
+if ($serverAction -eq "StartServer")
+{
+	Write-Host "Starting the application on machine '$serverName'"
+	$startServerScriptPath = "$PSScriptRoot\StartServer.ps1"
+	$remoteResult=Invoke-Command -Session $psSession -FilePath $startServerScriptPath -ArgumentList $deployedFolderPath, $dotnetRuntimePath, $executablePath, $executableParameters, $serverType, $serverName, $applicationBaseUrl, $environmentVariables
+}
+else
+{
+	Write-Host "Stopping the application on machine '$serverName'"
+	$stopServerScriptPath = "$PSScriptRoot\StopServer.ps1"
+	$serverProcessName = [System.IO.Path]::GetFileNameWithoutExtension($executablePath)
+	$remoteResult=Invoke-Command -Session $psSession -FilePath $stopServerScriptPath -ArgumentList $deployedFolderPath, $serverProcessName, $serverType, $serverName
+}
+
+Remove-PSSession $psSession
+
+# NOTE: Currenty there is no straight forward way to get the exit code from a remotely executing session, so
+# we print out the exit code in the remote script and capture it's output to get the exit code.
+if($remoteResult.Length > 0)
+{
+    $finalExitCode=$remoteResult[$remoteResult.Length-1]
+    exit $finalExitCode
+}
\ No newline at end of file
diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/RemoteWindowsDeployer.cs b/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/RemoteWindowsDeployer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a102cd02da371fa6d6f8a0c2d2098c9dde01bcf2
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/RemoteWindowsDeployer.cs
@@ -0,0 +1,361 @@
+// 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.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml.Linq;
+using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
+using Microsoft.Extensions.FileProviders;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    public class RemoteWindowsDeployer : ApplicationDeployer
+    {
+        /// <summary>
+        /// Example: If the share path is '\\dir1\dir2', then this returns the full path to the
+        /// deployed folder. Example: '\\dir1\dir2\048f6c99-de3e-488a-8020-f9eb277818d9'
+        /// </summary>
+        private string _deployedFolderPathInFileShare;
+        private readonly RemoteWindowsDeploymentParameters _deploymentParameters;
+        private bool _isDisposed;
+        private static readonly Lazy<Scripts> _scripts = new Lazy<Scripts>(() => CopyEmbeddedScriptFilesToDisk());
+
+        public RemoteWindowsDeployer(RemoteWindowsDeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
+            : base(deploymentParameters, loggerFactory)
+        {
+            _deploymentParameters = deploymentParameters;
+
+            if (_deploymentParameters.ServerType != ServerType.IIS
+                && _deploymentParameters.ServerType != ServerType.Kestrel
+                && _deploymentParameters.ServerType != ServerType.WebListener)
+            {
+                throw new InvalidOperationException($"Server type {_deploymentParameters.ServerType} is not supported for remote deployment." +
+                    $" Supported server types are {nameof(ServerType.Kestrel)}, {nameof(ServerType.IIS)} and {nameof(ServerType.WebListener)}");
+            }
+
+            if (string.IsNullOrWhiteSpace(_deploymentParameters.ServerName))
+            {
+                throw new ArgumentException($"Invalid value '{_deploymentParameters.ServerName}' for {nameof(RemoteWindowsDeploymentParameters.ServerName)}");
+            }
+
+            if (string.IsNullOrWhiteSpace(_deploymentParameters.ServerAccountName))
+            {
+                throw new ArgumentException($"Invalid value '{_deploymentParameters.ServerAccountName}' for {nameof(RemoteWindowsDeploymentParameters.ServerAccountName)}." +
+                    " Account credentials are required to enable creating a powershell session to the remote server.");
+            }
+
+            if (string.IsNullOrWhiteSpace(_deploymentParameters.ServerAccountPassword))
+            {
+                throw new ArgumentException($"Invalid value '{_deploymentParameters.ServerAccountPassword}' for {nameof(RemoteWindowsDeploymentParameters.ServerAccountPassword)}." +
+                    " Account credentials are required to enable creating a powershell session to the remote server.");
+            }
+
+            if (_deploymentParameters.ApplicationType == ApplicationType.Portable
+                && string.IsNullOrWhiteSpace(_deploymentParameters.DotnetRuntimePath))
+            {
+                throw new ArgumentException($"Invalid value '{_deploymentParameters.DotnetRuntimePath}' for {nameof(RemoteWindowsDeploymentParameters.DotnetRuntimePath)}. " +
+                    "It must be non-empty for portable apps.");
+            }
+
+            if (string.IsNullOrWhiteSpace(_deploymentParameters.RemoteServerFileSharePath))
+            {
+                throw new ArgumentException($"Invalid value for {nameof(RemoteWindowsDeploymentParameters.RemoteServerFileSharePath)}." +
+                    " . A file share is required to copy the application's published output.");
+            }
+
+            if (string.IsNullOrWhiteSpace(_deploymentParameters.ApplicationBaseUriHint))
+            {
+                throw new ArgumentException($"Invalid value for {nameof(RemoteWindowsDeploymentParameters.ApplicationBaseUriHint)}.");
+            }
+        }
+
+        public override async Task<DeploymentResult> DeployAsync()
+        {
+            using (Logger.BeginScope("Deploy"))
+            {
+                if (_isDisposed)
+                {
+                    throw new ObjectDisposedException("This instance of deployer has already been disposed.");
+                }
+
+                // Publish the app to a local temp folder on the machine where the test is running
+                DotnetPublish();
+
+                if (_deploymentParameters.ServerType == ServerType.IIS)
+                {
+                    UpdateWebConfig();
+                }
+
+                var folderId = Guid.NewGuid().ToString();
+                _deployedFolderPathInFileShare = Path.Combine(_deploymentParameters.RemoteServerFileSharePath, folderId);
+
+                DirectoryCopy(
+                    _deploymentParameters.PublishedApplicationRootPath,
+                    _deployedFolderPathInFileShare,
+                    copySubDirs: true);
+                Logger.LogInformation($"Copied the locally published folder to the file share path '{_deployedFolderPathInFileShare}'");
+
+                await RunScriptAsync("StartServer");
+
+                return new DeploymentResult(
+                    LoggerFactory,
+                    DeploymentParameters,
+                    DeploymentParameters.ApplicationBaseUriHint);
+            }
+        }
+
+        public override void Dispose()
+        {
+            using (Logger.BeginScope("Dispose"))
+            {
+                if (_isDisposed)
+                {
+                    return;
+                }
+
+                _isDisposed = true;
+
+                try
+                {
+                    Logger.LogInformation($"Stopping the application on the server '{_deploymentParameters.ServerName}'");
+                    RunScriptAsync("StopServer").Wait();
+                }
+                catch (Exception ex)
+                {
+                    Logger.LogWarning(0, "Failed to stop the server.", ex);
+                }
+
+                try
+                {
+                    Logger.LogInformation($"Deleting the deployed folder '{_deployedFolderPathInFileShare}'");
+                    Directory.Delete(_deployedFolderPathInFileShare, recursive: true);
+                }
+                catch (Exception ex)
+                {
+                    Logger.LogWarning(0, $"Failed to delete the deployed folder '{_deployedFolderPathInFileShare}'.", ex);
+                }
+
+                try
+                {
+                    Logger.LogInformation($"Deleting the locally published folder '{DeploymentParameters.PublishedApplicationRootPath}'");
+                    Directory.Delete(DeploymentParameters.PublishedApplicationRootPath, recursive: true);
+                }
+                catch (Exception ex)
+                {
+                    Logger.LogWarning(0, $"Failed to delete the locally published folder '{DeploymentParameters.PublishedApplicationRootPath}'.", ex);
+                }
+            }
+        }
+
+        private void UpdateWebConfig()
+        {
+            var webConfigFilePath = Path.Combine(_deploymentParameters.PublishedApplicationRootPath, "web.config");
+            var webConfig = XDocument.Load(webConfigFilePath);
+            var aspNetCoreSection = webConfig.Descendants("aspNetCore")
+                .Single();
+
+            // if the dotnet runtime path is specified, update the published web.config file to have that path
+            if (!string.IsNullOrEmpty(_deploymentParameters.DotnetRuntimePath))
+            {
+                aspNetCoreSection.SetAttributeValue(
+                    "processPath",
+                    Path.Combine(_deploymentParameters.DotnetRuntimePath, "dotnet.exe"));
+            }
+
+            var environmentVariablesSection = aspNetCoreSection.Elements("environmentVariables").FirstOrDefault();
+            if (environmentVariablesSection == null)
+            {
+                environmentVariablesSection = new XElement("environmentVariables");
+                aspNetCoreSection.Add(environmentVariablesSection);
+            }
+
+            foreach (var envVariablePair in _deploymentParameters.EnvironmentVariables)
+            {
+                var environmentVariable = new XElement("environmentVariable");
+                environmentVariable.SetAttributeValue("name", envVariablePair.Key);
+                environmentVariable.SetAttributeValue("value", envVariablePair.Value);
+                environmentVariablesSection.Add(environmentVariable);
+            }
+
+            if(Logger.IsEnabled(LogLevel.Trace))
+            {
+                Logger.LogTrace($"Config File Content:{Environment.NewLine}===START CONFIG==={Environment.NewLine}{{configContent}}{Environment.NewLine}===END CONFIG===", webConfig.ToString());
+            }
+
+            using (var fileStream = File.Open(webConfigFilePath, FileMode.Open))
+            {
+                webConfig.Save(fileStream);
+            }
+        }
+
+        private async Task RunScriptAsync(string serverAction)
+        {
+            using (Logger.BeginScope($"RunScript:{serverAction}"))
+            {
+                var remotePSSessionHelperScript = _scripts.Value.RemotePSSessionHelper;
+
+                string executablePath = null;
+                string executableParameters = null;
+                var applicationName = new DirectoryInfo(DeploymentParameters.ApplicationPath).Name;
+                if (DeploymentParameters.ApplicationType == ApplicationType.Portable)
+                {
+                    executablePath = "dotnet.exe";
+                    executableParameters = Path.Combine(_deployedFolderPathInFileShare, applicationName + ".dll");
+                }
+                else
+                {
+                    executablePath = Path.Combine(_deployedFolderPathInFileShare, applicationName + ".exe");
+                }
+
+                var parameterBuilder = new StringBuilder();
+                parameterBuilder.Append($"\"{remotePSSessionHelperScript}\"");
+                parameterBuilder.Append($" -serverName {_deploymentParameters.ServerName}");
+                parameterBuilder.Append($" -accountName {_deploymentParameters.ServerAccountName}");
+                parameterBuilder.Append($" -accountPassword {_deploymentParameters.ServerAccountPassword}");
+                parameterBuilder.Append($" -deployedFolderPath {_deployedFolderPathInFileShare}");
+
+                if (!string.IsNullOrEmpty(_deploymentParameters.DotnetRuntimePath))
+                {
+                    parameterBuilder.Append($" -dotnetRuntimePath \"{_deploymentParameters.DotnetRuntimePath}\"");
+                }
+
+                parameterBuilder.Append($" -executablePath \"{executablePath}\"");
+
+                if (!string.IsNullOrEmpty(executableParameters))
+                {
+                    parameterBuilder.Append($" -executableParameters \"{executableParameters}\"");
+                }
+
+                parameterBuilder.Append($" -serverType {_deploymentParameters.ServerType}");
+                parameterBuilder.Append($" -serverAction {serverAction}");
+                parameterBuilder.Append($" -applicationBaseUrl {_deploymentParameters.ApplicationBaseUriHint}");
+                var environmentVariables = string.Join("`,", _deploymentParameters.EnvironmentVariables.Select(envVariable => $"{envVariable.Key}={envVariable.Value}"));
+                parameterBuilder.Append($" -environmentVariables \"{environmentVariables}\"");
+
+                var startInfo = new ProcessStartInfo
+                {
+                    FileName = "powershell.exe",
+                    Arguments = parameterBuilder.ToString(),
+                    UseShellExecute = false,
+                    CreateNoWindow = true,
+                    RedirectStandardError = true,
+                    RedirectStandardOutput = true,
+                    RedirectStandardInput = true
+                };
+
+                using (var runScriptsOnRemoteServerProcess = new Process() { StartInfo = startInfo })
+                {
+                    runScriptsOnRemoteServerProcess.EnableRaisingEvents = true;
+                    runScriptsOnRemoteServerProcess.Exited += (sender, exitedArgs) =>
+                    {
+                        Logger.LogInformation($"[{_deploymentParameters.ServerName} {serverAction} stdout]: script complete");
+                    };
+
+                    runScriptsOnRemoteServerProcess.StartAndCaptureOutAndErrToLogger(serverAction, Logger);
+
+                    // Wait a second for the script to run or fail. The StartServer script will only terminate when the Deployer is disposed,
+                    // so we don't want to wait for it to terminate here because it would deadlock.
+                    await Task.Delay(TimeSpan.FromMinutes(1));
+
+                    if (runScriptsOnRemoteServerProcess.HasExited && runScriptsOnRemoteServerProcess.ExitCode != 0)
+                    {
+                        throw new Exception($"Failed to execute the script on '{_deploymentParameters.ServerName}'.");
+                    }
+                }
+            }
+        }
+
+        private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs)
+        {
+            var dir = new DirectoryInfo(sourceDirName);
+
+            if (!dir.Exists)
+            {
+                throw new DirectoryNotFoundException(
+                    "Source directory does not exist or could not be found: "
+                    + sourceDirName);
+            }
+
+            var dirs = dir.GetDirectories();
+            if (!Directory.Exists(destDirName))
+            {
+                Directory.CreateDirectory(destDirName);
+            }
+
+            var files = dir.GetFiles();
+            foreach (var file in files)
+            {
+                var temppath = Path.Combine(destDirName, file.Name);
+                file.CopyTo(temppath, false);
+            }
+
+            if (copySubDirs)
+            {
+                foreach (var subdir in dirs)
+                {
+                    var temppath = Path.Combine(destDirName, subdir.Name);
+                    DirectoryCopy(subdir.FullName, temppath, copySubDirs);
+                }
+            }
+        }
+
+        private static Scripts CopyEmbeddedScriptFilesToDisk()
+        {
+            var embeddedFileNames = new[] { "RemotePSSessionHelper.ps1", "StartServer.ps1", "StopServer.ps1" };
+
+            // Copy the scripts from this assembly's embedded resources to the temp path on the machine where these
+            // tests are being run
+            var assembly = typeof(RemoteWindowsDeployer).GetTypeInfo().Assembly;
+            var embeddedFileProvider = new EmbeddedFileProvider(
+                assembly,
+                $"{assembly.GetName().Name}.Deployers.RemoteWindowsDeployer");
+
+            var filesOnDisk = new string[embeddedFileNames.Length];
+            for (var i = 0; i < embeddedFileNames.Length; i++)
+            {
+                var embeddedFileName = embeddedFileNames[i];
+                var physicalFilePath = Path.Combine(Path.GetTempPath(), embeddedFileName);
+                var sourceStream = embeddedFileProvider
+                    .GetFileInfo(embeddedFileName)
+                    .CreateReadStream();
+
+                using (sourceStream)
+                {
+                    var destinationStream = File.Create(physicalFilePath);
+                    using (destinationStream)
+                    {
+                        sourceStream.CopyTo(destinationStream);
+                    }
+                }
+
+                filesOnDisk[i] = physicalFilePath;
+            }
+
+            var scripts = new Scripts(filesOnDisk[0], filesOnDisk[1], filesOnDisk[2]);
+
+            return scripts;
+        }
+
+        private class Scripts
+        {
+            public Scripts(string remotePSSessionHelper, string startServer, string stopServer)
+            {
+                RemotePSSessionHelper = remotePSSessionHelper;
+                StartServer = startServer;
+                StopServer = stopServer;
+            }
+
+            public string RemotePSSessionHelper { get; }
+
+            public string StartServer { get; }
+
+            public string StopServer { get; }
+        }
+    }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/RemoteWindowsDeploymentParameters.cs b/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/RemoteWindowsDeploymentParameters.cs
new file mode 100644
index 0000000000000000000000000000000000000000..61d3a49325fd346cdcbbe954787838c20cdcba09
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/RemoteWindowsDeploymentParameters.cs
@@ -0,0 +1,40 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// See License.txt in the project root for license information
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    public class RemoteWindowsDeploymentParameters : DeploymentParameters
+    {
+        public RemoteWindowsDeploymentParameters(
+            string applicationPath,
+            string dotnetRuntimePath,
+            ServerType serverType,
+            RuntimeFlavor runtimeFlavor,
+            RuntimeArchitecture runtimeArchitecture,
+            string remoteServerFileSharePath,
+            string remoteServerName,
+            string remoteServerAccountName,
+            string remoteServerAccountPassword)
+            : base(applicationPath, serverType, runtimeFlavor, runtimeArchitecture)
+        {
+            RemoteServerFileSharePath = remoteServerFileSharePath;
+            ServerName = remoteServerName;
+            ServerAccountName = remoteServerAccountName;
+            ServerAccountPassword = remoteServerAccountPassword;
+            DotnetRuntimePath = dotnetRuntimePath;
+        }
+
+        public string ServerName { get; }
+
+        public string ServerAccountName { get; }
+
+        public string ServerAccountPassword { get; }
+
+        public string DotnetRuntimePath { get; }
+
+        /// <summary>
+        /// The full path to the remote server's file share
+        /// </summary>
+        public string RemoteServerFileSharePath { get; }
+    }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/StartServer.ps1 b/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/StartServer.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..0bcfea647c7a95e56961e66c52c84f086c584241
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/StartServer.ps1
@@ -0,0 +1,82 @@
+[CmdletBinding()]
+param(
+	[Parameter(Mandatory=$true)]
+	[string]$deployedFolderPath,
+
+	[Parameter(Mandatory=$false)]
+	[string]$dotnetRuntimePath,
+
+	[Parameter(Mandatory=$true)]
+	[string]$executablePath,
+
+	[Parameter(Mandatory=$false)]
+	[string]$executableParameters,
+
+	[Parameter(Mandatory=$true)]
+	[string]$serverType,
+
+	[Parameter(Mandatory=$true)]
+	[string]$serverName,
+
+	[Parameter(Mandatory=$true)]
+	[string]$applicationBaseUrl,
+
+	# These are of the format: key1=value1,key2=value2,key3=value3
+	[Parameter(Mandatory=$false)]
+	[string]$environmentVariables
+)
+
+Write-Host "Executing the start server script on machine '$serverName'"
+
+if ($serverType -eq "IIS")
+{
+    $publishedDirName=Split-Path $deployedFolderPath -Leaf
+    Write-Host "Creating IIS website '$publishedDirName' for path '$deployedFolderPath'"
+    Import-Module IISAdministration
+    $port=([System.Uri]$applicationBaseUrl).Port
+    $bindingPort="*:" + $port + ":"
+    New-IISSite -Name $publishedDirName -BindingInformation $bindingPort -PhysicalPath $deployedFolderPath
+}
+elseif (($serverType -eq "Kestrel") -or ($serverType -eq "WebListener"))
+{
+    if (-Not [string]::IsNullOrWhitespace($environmentVariables))
+    {
+        Write-Host "Setting up environment variables"
+        foreach ($envVariablePair in $environmentVariables.Split(","))
+        {
+	        $pair=$envVariablePair.Split("=");
+	        [Environment]::SetEnvironmentVariable($pair[0], $pair[1])
+        }
+    }
+
+    if ($executablePath -eq "dotnet.exe")
+    {
+        Write-Host "Setting the dotnet runtime path to the PATH environment variable"
+        [Environment]::SetEnvironmentVariable("PATH", "$dotnetRuntimePath")
+    }
+
+    # Change the current working directory to the deployed folder to make applications work 
+    # when they use API like Directory.GetCurrentDirectory()
+    cd -Path $deployedFolderPath
+
+    $command = $executablePath + " " + $executableParameters + " --server.urls " + $applicationBaseUrl
+    if ($serverType -eq "Kestrel")
+    {
+	    $command = $command + " --server Microsoft.AspNetCore.Server.Kestrel"
+	    Write-Host "Executing the command '$command'"
+	    Invoke-Expression $command
+    }
+    elseif ($serverType -eq "WebListener")
+    {
+	    $command = $command + " --server Microsoft.AspNetCore.Server.HttpSys"
+	    Write-Host "Executing the command '$command'"
+	    Invoke-Expression $command
+    }
+}
+else
+{
+    throw [System.InvalidOperationException] "Server type '$serverType' is not supported."
+}
+
+# NOTE: Make sure this is the last statement in this script as its used to get the exit code of this script
+$LASTEXITCODE
\ No newline at end of file
diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/StopServer.ps1 b/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/StopServer.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..9b66f883c229df213c753b8f2b6218b311139070
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/StopServer.ps1
@@ -0,0 +1,68 @@
+[CmdletBinding()]
+param(
+	[Parameter(Mandatory=$true)]
+	[string]$deployedFolderPath,
+
+	[Parameter(Mandatory=$true)]
+	[string]$serverProcessName,
+
+	[Parameter(Mandatory=$true)]
+	[string]$serverType,
+
+	[Parameter(Mandatory=$true)]
+	[string]$serverName
+)
+
+function DoesCommandExist($command)
+{
+	$oldPreference = $ErrorActionPreference
+	$ErrorActionPreference="stop"
+
+	try
+	{
+		if (Get-Command $command)
+		{
+			return $true
+		}
+	}
+	catch
+	{
+		Write-Host "Command '$command' does not exist"
+		return $false
+	}
+	finally
+	{
+		$ErrorActionPreference=$oldPreference
+	}
+}
+
+Write-Host "Executing the stop server script on machine '$serverName'"
+
+if ($serverType -eq "IIS")
+{
+	$publishedDirName=Split-Path $deployedFolderPath -Leaf
+	Write-Host "Stopping the IIS website '$publishedDirName'"
+	Import-Module IISAdministration
+	Stop-IISSite -Name $publishedDirName -Confirm:$false
+	Remove-IISSite -Name $publishedDirName -Confirm:$false
+    net stop w3svc
+    net start w3svc
+}
+else
+{
+    Write-Host "Stopping the process '$serverProcessName'"
+    $serverProcess=Get-Process -Name "$serverProcessName"
+
+    if (DoesCommandExist("taskkill"))
+    {
+	    # Kill the parent and child processes
+	    & taskkill /pid $serverProcess.Id /t /f
+    }
+    else
+    {
+	    Stop-Process -Id $serverProcess.Id -Force
+    }
+}
+
+# NOTE: Make sure this is the last statement in this script as its used to get the exit code of this script
+$LASTEXITCODE
\ No newline at end of file
diff --git a/src/Hosting/Server.IntegrationTesting/src/Deployers/SelfHostDeployer.cs b/src/Hosting/Server.IntegrationTesting/src/Deployers/SelfHostDeployer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..12f2b83de1ffa8ad06ada6be2e6a7c49b02770c2
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Deployers/SelfHostDeployer.cs
@@ -0,0 +1,200 @@
+// 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.IO;
+using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Server.IntegrationTesting.Common;
+using Microsoft.AspNetCore.Testing;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    /// <summary>
+    /// Deployer for WebListener and Kestrel.
+    /// </summary>
+    public class SelfHostDeployer : ApplicationDeployer
+    {
+        private static readonly Regex NowListeningRegex = new Regex(@"^\s*Now listening on: (?<url>.*)$");
+        private const string ApplicationStartedMessage = "Application started. Press Ctrl+C to shut down.";
+
+        public Process HostProcess { get; private set; }
+
+        public SelfHostDeployer(DeploymentParameters deploymentParameters, ILoggerFactory loggerFactory)
+            : base(deploymentParameters, loggerFactory)
+        {
+        }
+
+        public override async Task<DeploymentResult> DeployAsync()
+        {
+            using (Logger.BeginScope("SelfHost.Deploy"))
+            {
+                // Start timer
+                StartTimer();
+
+                if (DeploymentParameters.PublishApplicationBeforeDeployment)
+                {
+                    DotnetPublish();
+                }
+
+                var hintUrl = TestUriHelper.BuildTestUri(
+                    DeploymentParameters.ApplicationBaseUriHint,
+                    DeploymentParameters.ServerType,
+                    DeploymentParameters.StatusMessagesEnabled);
+
+                // Launch the host process.
+                var (actualUrl, hostExitToken) = await StartSelfHostAsync(hintUrl);
+
+                Logger.LogInformation("Application ready at URL: {appUrl}", actualUrl);
+
+                return new DeploymentResult(
+                    LoggerFactory,
+                    DeploymentParameters,
+                    applicationBaseUri: actualUrl.ToString(),
+                    contentRoot: DeploymentParameters.PublishApplicationBeforeDeployment ? DeploymentParameters.PublishedApplicationRootPath : DeploymentParameters.ApplicationPath,
+                    hostShutdownToken: hostExitToken);
+            }
+        }
+
+        protected async Task<(Uri url, CancellationToken hostExitToken)> StartSelfHostAsync(Uri hintUrl)
+        {
+            using (Logger.BeginScope("StartSelfHost"))
+            {
+                string executableName;
+                string executableArgs = string.Empty;
+                string workingDirectory = string.Empty;
+                if (DeploymentParameters.PublishApplicationBeforeDeployment)
+                {
+                    workingDirectory = DeploymentParameters.PublishedApplicationRootPath;
+                    var executableExtension =
+                        DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr ? ".exe" :
+                        DeploymentParameters.ApplicationType == ApplicationType.Portable ? ".dll" : "";
+                    var executable = Path.Combine(DeploymentParameters.PublishedApplicationRootPath, DeploymentParameters.ApplicationName + executableExtension);
+
+                    if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr && !RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+                    {
+                        executableName = "mono";
+                        executableArgs = executable;
+                    }
+                    else if (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.CoreClr && DeploymentParameters.ApplicationType == ApplicationType.Portable)
+                    {
+                        executableName = "dotnet";
+                        executableArgs = executable;
+                    }
+                    else
+                    {
+                        executableName = executable;
+                    }
+                }
+                else
+                {
+                    workingDirectory = DeploymentParameters.ApplicationPath;
+                    var targetFramework = DeploymentParameters.TargetFramework ?? (DeploymentParameters.RuntimeFlavor == RuntimeFlavor.Clr ? "net461" : "netcoreapp2.0");
+
+                    executableName = DotnetCommandName;
+                    executableArgs = $"run --no-build -c {DeploymentParameters.Configuration} --framework {targetFramework} {DotnetArgumentSeparator}";
+                }
+
+                executableArgs += $" --server.urls {hintUrl} "
+                + $" --server {(DeploymentParameters.ServerType == ServerType.WebListener ? "Microsoft.AspNetCore.Server.HttpSys" : "Microsoft.AspNetCore.Server.Kestrel")}";
+
+                Logger.LogInformation($"Executing {executableName} {executableArgs}");
+
+                var startInfo = new ProcessStartInfo
+                {
+                    FileName = executableName,
+                    Arguments = executableArgs,
+                    UseShellExecute = false,
+                    CreateNoWindow = true,
+                    RedirectStandardError = true,
+                    RedirectStandardOutput = true,
+                    // Trying a work around for https://github.com/aspnet/Hosting/issues/140.
+                    RedirectStandardInput = true,
+                    WorkingDirectory = workingDirectory
+                };
+
+                AddEnvironmentVariablesToProcess(startInfo, DeploymentParameters.EnvironmentVariables);
+
+                Uri actualUrl = null;
+                var started = new TaskCompletionSource<object>();
+
+                HostProcess = new Process() { StartInfo = startInfo };
+                HostProcess.EnableRaisingEvents = true;
+                HostProcess.OutputDataReceived += (sender, dataArgs) =>
+                {
+                    if (string.Equals(dataArgs.Data, ApplicationStartedMessage))
+                    {
+                        started.TrySetResult(null);
+                    }
+                    else if (!string.IsNullOrEmpty(dataArgs.Data))
+                    {
+                        var m = NowListeningRegex.Match(dataArgs.Data);
+                        if (m.Success)
+                        {
+                            actualUrl = new Uri(m.Groups["url"].Value);
+                        }
+                    }
+                };
+                var hostExitTokenSource = new CancellationTokenSource();
+                HostProcess.Exited += (sender, e) =>
+                {
+                    Logger.LogInformation("host process ID {pid} shut down", HostProcess.Id);
+
+                    // If TrySetResult was called above, this will just silently fail to set the new state, which is what we want
+                    started.TrySetException(new Exception($"Command exited unexpectedly with exit code: {HostProcess.ExitCode}"));
+
+                    TriggerHostShutdown(hostExitTokenSource);
+                };
+
+                try
+                {
+                    HostProcess.StartAndCaptureOutAndErrToLogger(executableName, Logger);
+                }
+                catch (Exception ex)
+                {
+                    Logger.LogError("Error occurred while starting the process. Exception: {exception}", ex.ToString());
+                }
+
+                if (HostProcess.HasExited)
+                {
+                    Logger.LogError("Host process {processName} {pid} exited with code {exitCode} or failed to start.", startInfo.FileName, HostProcess.Id, HostProcess.ExitCode);
+                    throw new Exception("Failed to start host");
+                }
+
+                Logger.LogInformation("Started {fileName}. Process Id : {processId}", startInfo.FileName, HostProcess.Id);
+
+                // Host may not write startup messages, in which case assume it started
+                if (DeploymentParameters.StatusMessagesEnabled)
+                {
+                    // The timeout here is large, because we don't know how long the test could need
+                    // We cover a lot of error cases above, but I want to make sure we eventually give up and don't hang the build
+                    // just in case we missed one -anurse
+                    await started.Task.TimeoutAfter(TimeSpan.FromMinutes(10));
+                }
+
+                return (url: actualUrl ?? hintUrl, hostExitToken: hostExitTokenSource.Token);
+            }
+        }
+
+        public override void Dispose()
+        {
+            using (Logger.BeginScope("SelfHost.Dispose"))
+            {
+                ShutDownIfAnyHostProcess(HostProcess);
+
+                if (DeploymentParameters.PublishApplicationBeforeDeployment)
+                {
+                    CleanPublishedOutput();
+                }
+
+                InvokeUserApplicationCleanup();
+
+                StopTimer();
+            }
+        }
+    }
+}
diff --git a/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj b/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..e693a222784e3ed8f2142a6e37a6f196f959f800
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj
@@ -0,0 +1,34 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>ASP.NET Core helpers to deploy applications to IIS Express, IIS, WebListener and Kestrel for testing.</Description>
+    <VersionPrefix Condition="'$(ExperimentalVersionPrefix)' != ''">$(ExperimentalVersionPrefix)</VersionPrefix>
+    <VersionSuffix Condition="'$(ExperimentalVersionSuffix)' != ''">$(ExperimentalVersionSuffix)</VersionSuffix>
+    <VerifyVersion Condition="'$(ExperimentalVersionPrefix)' != ''">false</VerifyVersion>
+    <PackageVersion Condition="'$(ExperimentalPackageVersion)' != ''">$(ExperimentalPackageVersion)</PackageVersion>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <NoWarn>$(NoWarn);CS1591</NoWarn>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore;testing</PackageTags>
+    <EnableApiCheck>false</EnableApiCheck>
+    <UseLatestPackageReferences>true</UseLatestPackageReferences>
+    <IsPackable>false</IsPackable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <EmbeddedResource Include="Deployers\RemoteWindowsDeployer\RemotePSSessionHelper.ps1;Deployers\RemoteWindowsDeployer\StartServer.ps1;Deployers\RemoteWindowsDeployer\StopServer.ps1" Exclude="bin\**;obj\**;**\*.xproj;packages\**;@(EmbeddedResource)" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Testing" />
+    <Reference Include="Microsoft.Extensions.FileProviders.Embedded" />
+    <Reference Include="Microsoft.Extensions.Logging" />
+    <Reference Include="Microsoft.Extensions.Logging.Console" />
+    <Reference Include="Microsoft.Extensions.Logging.Testing" />
+    <Reference Include="Microsoft.Extensions.Process.Sources" PrivateAssets="All" />
+    <Reference Include="Microsoft.NETCore.Windows.ApiSets" />
+    <Reference Include="Serilog.Extensions.Logging" />
+    <Reference Include="Serilog.Sinks.File" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Hosting/Server.IntegrationTesting/src/baseline.netcore.json b/src/Hosting/Server.IntegrationTesting/src/baseline.netcore.json
new file mode 100644
index 0000000000000000000000000000000000000000..9e26dfeeb6e641a33dae4961196235bdb965b21b
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/baseline.netcore.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/src/Hosting/Server.IntegrationTesting/src/xunit/SkipIfEnvironmentVariableNotEnabled.cs b/src/Hosting/Server.IntegrationTesting/src/xunit/SkipIfEnvironmentVariableNotEnabled.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8d54d6ac709e075531d9179b3a20c39649aa8919
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/xunit/SkipIfEnvironmentVariableNotEnabled.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 Microsoft.AspNetCore.Testing.xunit;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    /// <summary>
+    /// Skip test if a given environment variable is not enabled. To enable the test, set environment variable
+    /// to "true" for the test process.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
+    public class SkipIfEnvironmentVariableNotEnabledAttribute : Attribute, ITestCondition
+    {
+        private readonly string _environmentVariableName;
+
+        public SkipIfEnvironmentVariableNotEnabledAttribute(string environmentVariableName)
+        {
+            _environmentVariableName = environmentVariableName;
+        }
+
+        public bool IsMet
+        {
+            get
+            {
+                return string.Compare(Environment.GetEnvironmentVariable(_environmentVariableName), "true", ignoreCase: true) == 0;
+            }
+        }
+
+        public string SkipReason
+        {
+            get
+            {
+                return $"To run this test, set the environment variable {_environmentVariableName}=\"true\". {AdditionalInfo}";
+            }
+        }
+
+        public string AdditionalInfo { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/Server.IntegrationTesting/src/xunit/SkipOn32BitOSAttribute.cs b/src/Hosting/Server.IntegrationTesting/src/xunit/SkipOn32BitOSAttribute.cs
new file mode 100644
index 0000000000000000000000000000000000000000..554cc63edafe5e993fa785ad180dce07d309f8b4
--- /dev/null
+++ b/src/Hosting/Server.IntegrationTesting/src/xunit/SkipOn32BitOSAttribute.cs
@@ -0,0 +1,33 @@
+// 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 Microsoft.AspNetCore.Testing.xunit;
+
+namespace Microsoft.AspNetCore.Server.IntegrationTesting
+{
+    /// <summary>
+    /// Skips a 64 bit test if the current Windows OS is 32-bit.
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
+    public class SkipOn32BitOSAttribute : Attribute, ITestCondition
+    {
+        public bool IsMet
+        {
+            get
+            {
+                // Directory found only on 64-bit OS.
+                return Directory.Exists(Path.Combine(Environment.GetEnvironmentVariable("SystemRoot"), "SysWOW64"));
+            }
+        }
+
+        public string SkipReason
+        {
+            get
+            {
+                return "Skipping the x64 test since Windows is 32-bit";
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/TestHost/src/ClientHandler.cs b/src/Hosting/TestHost/src/ClientHandler.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2109809d1a8e79d6d2eeafb9f824fc7515dcd07c
--- /dev/null
+++ b/src/Hosting/TestHost/src/ClientHandler.cs
@@ -0,0 +1,132 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Context = Microsoft.AspNetCore.Hosting.Internal.HostingApplication.Context;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+    /// <summary>
+    /// This adapts HttpRequestMessages to ASP.NET Core requests, dispatches them through the pipeline, and returns the
+    /// associated HttpResponseMessage.
+    /// </summary>
+    public class ClientHandler : HttpMessageHandler
+    {
+        private readonly IHttpApplication<Context> _application;
+        private readonly PathString _pathBase;
+
+        /// <summary>
+        /// Create a new handler.
+        /// </summary>
+        /// <param name="pathBase">The base path.</param>
+        /// <param name="application">The <see cref="IHttpApplication{TContext}"/>.</param>
+        public ClientHandler(PathString pathBase, IHttpApplication<Context> application)
+        {
+            _application = application ?? throw new ArgumentNullException(nameof(application));
+
+            // PathString.StartsWithSegments that we use below requires the base path to not end in a slash.
+            if (pathBase.HasValue && pathBase.Value.EndsWith("/"))
+            {
+                pathBase = new PathString(pathBase.Value.Substring(0, pathBase.Value.Length - 1));
+            }
+            _pathBase = pathBase;
+        }
+
+        /// <summary>
+        /// This adapts HttpRequestMessages to ASP.NET Core requests, dispatches them through the pipeline, and returns the
+        /// associated HttpResponseMessage.
+        /// </summary>
+        /// <param name="request"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        protected override async Task<HttpResponseMessage> SendAsync(
+            HttpRequestMessage request,
+            CancellationToken cancellationToken)
+        {
+            if (request == null)
+            {
+                throw new ArgumentNullException(nameof(request));
+            }
+
+            var contextBuilder = new HttpContextBuilder(_application);
+
+            Stream responseBody = null;
+            var requestContent = request.Content ?? new StreamContent(Stream.Null);
+            var body = await requestContent.ReadAsStreamAsync();
+            contextBuilder.Configure(context =>
+            {
+                var req = context.Request;
+
+                req.Protocol = "HTTP/" + request.Version.ToString(fieldCount: 2);
+                req.Method = request.Method.ToString();
+
+                req.Scheme = request.RequestUri.Scheme;
+                req.Host = HostString.FromUriComponent(request.RequestUri);
+                if (request.RequestUri.IsDefaultPort)
+                {
+                    req.Host = new HostString(req.Host.Host);
+                }
+
+                req.Path = PathString.FromUriComponent(request.RequestUri);
+                req.PathBase = PathString.Empty;
+                if (req.Path.StartsWithSegments(_pathBase, out var remainder))
+                {
+                    req.Path = remainder;
+                    req.PathBase = _pathBase;
+                }
+                req.QueryString = QueryString.FromUriComponent(request.RequestUri);
+
+                foreach (var header in request.Headers)
+                {
+                    req.Headers.Append(header.Key, header.Value.ToArray());
+                }
+                if (requestContent != null)
+                {
+                    foreach (var header in requestContent.Headers)
+                    {
+                        req.Headers.Append(header.Key, header.Value.ToArray());
+                    }
+                }
+
+                if (body.CanSeek)
+                {
+                    // This body may have been consumed before, rewind it.
+                    body.Seek(0, SeekOrigin.Begin);
+                }
+                req.Body = body;
+
+                responseBody = context.Response.Body;
+            });
+
+            var httpContext = await contextBuilder.SendAsync(cancellationToken);
+
+            var response = new HttpResponseMessage();
+            response.StatusCode = (HttpStatusCode)httpContext.Response.StatusCode;
+            response.ReasonPhrase = httpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase;
+            response.RequestMessage = request;
+
+            response.Content = new StreamContent(responseBody);
+
+            foreach (var header in httpContext.Response.Headers)
+            {
+                if (!response.Headers.TryAddWithoutValidation(header.Key, (IEnumerable<string>)header.Value))
+                {
+                    bool success = response.Content.Headers.TryAddWithoutValidation(header.Key, (IEnumerable<string>)header.Value);
+                    Contract.Assert(success, "Bad header");
+                }
+            }
+            return response;
+        }
+    }
+}
diff --git a/src/Hosting/TestHost/src/HttpContextBuilder.cs b/src/Hosting/TestHost/src/HttpContextBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6886b1aac45a8dc515fad74ee31aa32059bc045b
--- /dev/null
+++ b/src/Hosting/TestHost/src/HttpContextBuilder.cs
@@ -0,0 +1,130 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using static Microsoft.AspNetCore.Hosting.Internal.HostingApplication;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+    internal class HttpContextBuilder
+    {
+        private readonly IHttpApplication<Context> _application;
+        private readonly HttpContext _httpContext;
+        
+        private TaskCompletionSource<HttpContext> _responseTcs = new TaskCompletionSource<HttpContext>(TaskCreationOptions.RunContinuationsAsynchronously);
+        private ResponseStream _responseStream;
+        private ResponseFeature _responseFeature = new ResponseFeature();
+        private CancellationTokenSource _requestAbortedSource = new CancellationTokenSource();
+        private bool _pipelineFinished;
+        private Context _testContext;
+
+        internal HttpContextBuilder(IHttpApplication<Context> application)
+        {
+            _application = application ?? throw new ArgumentNullException(nameof(application));
+            _httpContext = new DefaultHttpContext();
+
+            var request = _httpContext.Request;
+            request.Protocol = "HTTP/1.1";
+            request.Method = HttpMethods.Get;
+
+            _httpContext.Features.Set<IHttpResponseFeature>(_responseFeature);
+            var requestLifetimeFeature = new HttpRequestLifetimeFeature();
+            requestLifetimeFeature.RequestAborted = _requestAbortedSource.Token;
+            _httpContext.Features.Set<IHttpRequestLifetimeFeature>(requestLifetimeFeature);
+            
+            _responseStream = new ResponseStream(ReturnResponseMessageAsync, AbortRequest);
+            _responseFeature.Body = _responseStream;
+        }
+
+        internal void Configure(Action<HttpContext> configureContext)
+        {
+            if (configureContext == null)
+            {
+                throw new ArgumentNullException(nameof(configureContext));
+            }
+
+            configureContext(_httpContext);
+        }
+
+        /// <summary>
+        /// Start processing the request.
+        /// </summary>
+        /// <returns></returns>
+        internal Task<HttpContext> SendAsync(CancellationToken cancellationToken)
+        {
+            var registration = cancellationToken.Register(AbortRequest);
+
+            _testContext = _application.CreateContext(_httpContext.Features);
+
+            // Async offload, don't let the test code block the caller.
+            _ = Task.Factory.StartNew(async () =>
+            {
+                try
+                {
+                    await _application.ProcessRequestAsync(_testContext);
+                    await CompleteResponseAsync();
+                    _application.DisposeContext(_testContext, exception: null);
+                }
+                catch (Exception ex)
+                {
+                    Abort(ex);
+                    _application.DisposeContext(_testContext, ex);
+                }
+                finally
+                {
+                    registration.Dispose();
+                }
+            });
+
+            return _responseTcs.Task;
+        }
+
+        internal void AbortRequest()
+        {
+            if (!_pipelineFinished)
+            {
+                _requestAbortedSource.Cancel();
+            }
+            _responseStream.CompleteWrites();
+        }
+
+        internal async Task CompleteResponseAsync()
+        {
+            _pipelineFinished = true;
+            await ReturnResponseMessageAsync();
+            _responseStream.CompleteWrites();
+            await _responseFeature.FireOnResponseCompletedAsync();
+        }
+
+        internal async Task ReturnResponseMessageAsync()
+        {
+            // Check if the response has already started because the TrySetResult below could happen a bit late
+            // (as it happens on a different thread) by which point the CompleteResponseAsync could run and calls this
+            // method again.
+            if (!_responseFeature.HasStarted)
+            {
+                // Sets HasStarted
+                await _responseFeature.FireOnSendingHeadersAsync();
+                // Copy the feature collection so we're not multi-threading on the same collection.
+                var newFeatures = new FeatureCollection();
+                foreach (var pair in _httpContext.Features)
+                {
+                    newFeatures[pair.Key] = pair.Value;
+                }
+                _responseTcs.TrySetResult(new DefaultHttpContext(newFeatures));
+            }
+        }
+
+        internal void Abort(Exception exception)
+        {
+            _pipelineFinished = true;
+            _responseStream.Abort(exception);
+            _responseTcs.TrySetException(exception);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/TestHost/src/Microsoft.AspNetCore.TestHost.csproj b/src/Hosting/TestHost/src/Microsoft.AspNetCore.TestHost.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..acb65b20062ec0004d532c12182eced6e86bdddd
--- /dev/null
+++ b/src/Hosting/TestHost/src/Microsoft.AspNetCore.TestHost.csproj
@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>ASP.NET Core web server for writing and running tests.</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <NoWarn>$(NoWarn);CS1591</NoWarn>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore;hosting;testing</PackageTags>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Include="$(SharedSourceRoot)Hosting.WebHostBuilderFactory\**\*.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Hosting" />
+    <Reference Include="System.IO.Pipelines" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Hosting/TestHost/src/Properties/AssemblyInfo.cs b/src/Hosting/TestHost/src/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5e17a096e7803190a7a65dafab46bcbe3d7778d6
--- /dev/null
+++ b/src/Hosting/TestHost/src/Properties/AssemblyInfo.cs
@@ -0,0 +1,10 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+[assembly: ComVisible(false)]
+[assembly: Guid("12A3EDBB-65B6-4D47-98FC-2B80CEC71E51")]
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.TestHost.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
+
diff --git a/src/Hosting/TestHost/src/RequestBuilder.cs b/src/Hosting/TestHost/src/RequestBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c770ec75babf4cba60a27d9ddc14eaa76f65f65a
--- /dev/null
+++ b/src/Hosting/TestHost/src/RequestBuilder.cs
@@ -0,0 +1,105 @@
+// 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.Net.Http;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+    /// <summary>
+    /// Used to construct a HttpRequestMessage object.
+    /// </summary>
+    public class RequestBuilder
+    {
+        private readonly TestServer _server;
+        private readonly HttpRequestMessage _req;
+
+        /// <summary>
+        /// Construct a new HttpRequestMessage with the given path.
+        /// </summary>
+        /// <param name="server"></param>
+        /// <param name="path"></param>
+        public RequestBuilder(TestServer server, string path)
+        {
+            if (server == null)
+            {
+                throw new ArgumentNullException(nameof(server));
+            }
+
+            _server = server;
+            _req = new HttpRequestMessage(HttpMethod.Get, path);
+        }
+
+        /// <summary>
+        /// Configure any HttpRequestMessage properties.
+        /// </summary>
+        /// <param name="configure"></param>
+        /// <returns></returns>
+        public RequestBuilder And(Action<HttpRequestMessage> configure)
+        {
+            if (configure == null)
+            {
+                throw new ArgumentNullException(nameof(configure));
+            }
+
+            configure(_req);
+            return this;
+        }
+
+        /// <summary>
+        /// Add the given header and value to the request or request content.
+        /// </summary>
+        /// <param name="name"></param>
+        /// <param name="value"></param>
+        /// <returns></returns>
+        public RequestBuilder AddHeader(string name, string value)
+        {
+            if (!_req.Headers.TryAddWithoutValidation(name, value))
+            {
+                if (_req.Content == null)
+                {
+                    _req.Content = new StreamContent(Stream.Null);
+                }
+                if (!_req.Content.Headers.TryAddWithoutValidation(name, value))
+                {
+                    // TODO: throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.InvalidHeaderName, name), "name");
+                    throw new ArgumentException("Invalid header name: " + name, "name");
+                }
+            }
+            return this;
+        }
+
+        /// <summary>
+        /// Set the request method and start processing the request.
+        /// </summary>
+        /// <param name="method"></param>
+        /// <returns></returns>
+        public Task<HttpResponseMessage> SendAsync(string method)
+        {
+            _req.Method = new HttpMethod(method);
+            return _server.CreateClient().SendAsync(_req);
+        }
+
+        /// <summary>
+        /// Set the request method to GET and start processing the request.
+        /// </summary>
+        /// <returns></returns>
+        public Task<HttpResponseMessage> GetAsync()
+        {
+            _req.Method = HttpMethod.Get;
+            return _server.CreateClient().SendAsync(_req);
+        }
+
+        /// <summary>
+        /// Set the request method to POST and start processing the request.
+        /// </summary>
+        /// <returns></returns>
+        public Task<HttpResponseMessage> PostAsync()
+        {
+            _req.Method = HttpMethod.Post;
+            return _server.CreateClient().SendAsync(_req);
+        }
+    }
+}
diff --git a/src/Hosting/TestHost/src/RequestFeature.cs b/src/Hosting/TestHost/src/RequestFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d634f2dbe2a7fce4106e4ae157c115e3abd0cc12
--- /dev/null
+++ b/src/Hosting/TestHost/src/RequestFeature.cs
@@ -0,0 +1,42 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+    internal class RequestFeature : IHttpRequestFeature
+    {
+        public RequestFeature()
+        {
+            Body = Stream.Null;
+            Headers = new HeaderDictionary();
+            Method = "GET";
+            Path = "";
+            PathBase = "";
+            Protocol = "HTTP/1.1";
+            QueryString = "";
+            Scheme = "http";
+        }
+
+        public Stream Body { get; set; }
+
+        public IHeaderDictionary Headers { get; set; }
+
+        public string Method { get; set; }
+
+        public string Path { get; set; }
+
+        public string PathBase { get; set; }
+
+        public string Protocol { get; set; }
+
+        public string QueryString { get; set; }
+
+        public string Scheme { get; set; }
+
+        public string RawTarget { get; set; }
+    }
+}
diff --git a/src/Hosting/TestHost/src/ResponseFeature.cs b/src/Hosting/TestHost/src/ResponseFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c6c7b47e18ddfe25022cf26423ec24f2db75d508
--- /dev/null
+++ b/src/Hosting/TestHost/src/ResponseFeature.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.IO;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+    internal class ResponseFeature : IHttpResponseFeature
+    {
+        private Func<Task> _responseStartingAsync = () => Task.FromResult(true);
+        private Func<Task> _responseCompletedAsync = () => Task.FromResult(true);
+        private HeaderDictionary _headers = new HeaderDictionary();
+        private int _statusCode;
+        private string _reasonPhrase;
+
+        public ResponseFeature()
+        {
+            Headers = _headers;
+            Body = new MemoryStream();
+
+            // 200 is the default status code all the way down to the host, so we set it
+            // here to be consistent with the rest of the hosts when writing tests.
+            StatusCode = 200;
+        }
+
+        public int StatusCode
+        {
+            get => _statusCode;
+            set
+            {
+                if (HasStarted)
+                {
+                    throw new InvalidOperationException("The status code cannot be set, the response has already started.");
+                }
+                if (value < 100)
+                {
+                    throw new ArgumentOutOfRangeException(nameof(value), value, "The status code cannot be set to a value less than 100");
+                }
+
+                _statusCode = value;
+            }
+        }
+
+        public string ReasonPhrase
+        {
+            get => _reasonPhrase;
+            set
+            {
+                if (HasStarted)
+                {
+                    throw new InvalidOperationException("The reason phrase cannot be set, the response has already started.");
+                }
+
+                _reasonPhrase = value;
+            }
+        }
+
+        public IHeaderDictionary Headers { get; set; }
+
+        public Stream Body { get; set; }
+
+        public bool HasStarted { get; set; }
+
+        public void OnStarting(Func<object, Task> callback, object state)
+        {
+            if (HasStarted)
+            {
+                throw new InvalidOperationException();
+            }
+
+            var prior = _responseStartingAsync;
+            _responseStartingAsync = async () =>
+            {
+                await callback(state);
+                await prior();
+            };
+        }
+
+        public void OnCompleted(Func<object, Task> callback, object state)
+        {
+            var prior = _responseCompletedAsync;
+            _responseCompletedAsync = async () =>
+            {
+                try
+                {
+                    await callback(state);
+                }
+                finally
+                {
+                    await prior();
+                }
+            };
+        }
+
+        public async Task FireOnSendingHeadersAsync()
+        {
+            await _responseStartingAsync();
+            HasStarted = true;
+            _headers.IsReadOnly = true;
+        }
+
+        public Task FireOnResponseCompletedAsync()
+        {
+            return _responseCompletedAsync();
+        }
+    }
+}
diff --git a/src/Hosting/TestHost/src/ResponseStream.cs b/src/Hosting/TestHost/src/ResponseStream.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0cd3459a80e841436208c6f6d0350f578e5aa1bb
--- /dev/null
+++ b/src/Hosting/TestHost/src/ResponseStream.cs
@@ -0,0 +1,256 @@
+// 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.Buffers;
+using System.Diagnostics;
+using System.Diagnostics.Contracts;
+using System.IO;
+using System.IO.Pipelines;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+    // This steam accepts writes from the server/app, buffers them internally, and returns the data via Reads
+    // when requested by the client.
+    internal class ResponseStream : Stream
+    {
+        private bool _complete;
+        private bool _aborted;
+        private Exception _abortException;
+        private SemaphoreSlim _writeLock;
+
+        private Func<Task> _onFirstWriteAsync;
+        private bool _firstWrite;
+        private Action _abortRequest;
+
+        private Pipe _pipe = new Pipe();
+
+        internal ResponseStream(Func<Task> onFirstWriteAsync, Action abortRequest)
+        {
+            _onFirstWriteAsync = onFirstWriteAsync ?? throw new ArgumentNullException(nameof(onFirstWriteAsync));
+            _abortRequest = abortRequest ?? throw new ArgumentNullException(nameof(abortRequest));
+            _firstWrite = true;
+            _writeLock = new SemaphoreSlim(1, 1);
+        }
+
+        public override bool CanRead
+        {
+            get { return true; }
+        }
+
+        public override bool CanSeek
+        {
+            get { return false; }
+        }
+
+        public override bool CanWrite
+        {
+            get { return true; }
+        }
+
+        #region NotSupported
+
+        public override long Length
+        {
+            get { throw new NotSupportedException(); }
+        }
+
+        public override long Position
+        {
+            get { throw new NotSupportedException(); }
+            set { throw new NotSupportedException(); }
+        }
+
+        public override long Seek(long offset, SeekOrigin origin)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override void SetLength(long value)
+        {
+            throw new NotSupportedException();
+        }
+
+        #endregion NotSupported
+
+        public override void Flush()
+        {
+            FlushAsync().GetAwaiter().GetResult();
+        }
+
+        public override async Task FlushAsync(CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+            CheckNotComplete();
+
+            await _writeLock.WaitAsync(cancellationToken);
+            try
+            {
+                await FirstWriteAsync();
+                await _pipe.Writer.FlushAsync(cancellationToken);
+            }
+            finally
+            {
+                _writeLock.Release();
+            }
+        }
+
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            return ReadAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult();
+        }
+
+        public async override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            VerifyBuffer(buffer, offset, count, allowEmpty: false);
+            CheckAborted();
+            var registration = cancellationToken.Register(Cancel);
+            try
+            {
+                // TODO: Usability issue. dotnet/corefx#27732 Flush or zero byte write causes ReadAsync to complete without data so I have to call ReadAsync in a loop.
+                while (true)
+                {
+                    var result = await _pipe.Reader.ReadAsync(cancellationToken);
+
+                    var readableBuffer = result.Buffer;
+                    if (!readableBuffer.IsEmpty)
+                    {
+                        var actual = Math.Min(readableBuffer.Length, count);
+                        readableBuffer = readableBuffer.Slice(0, actual);
+                        readableBuffer.CopyTo(new Span<byte>(buffer, offset, count));
+                        _pipe.Reader.AdvanceTo(readableBuffer.End, readableBuffer.End);
+                        return (int)actual;
+                    }
+
+                    if (result.IsCompleted)
+                    {
+                        _pipe.Reader.AdvanceTo(readableBuffer.End, readableBuffer.End); // TODO: Remove after https://github.com/dotnet/corefx/pull/27596
+                        _pipe.Reader.Complete();
+                        return 0;
+                    }
+
+                    cancellationToken.ThrowIfCancellationRequested();
+                    Debug.Assert(!result.IsCanceled); // It should only be canceled by cancellationToken.
+
+                    // Try again. TODO: dotnet/corefx#27732 I shouldn't need to do this, there wasn't any data.
+                    _pipe.Reader.AdvanceTo(readableBuffer.End, readableBuffer.End);
+                }
+            }
+            finally
+            {
+                registration.Dispose();
+            }
+        }
+
+        // Called under write-lock.
+        private Task FirstWriteAsync()
+        {
+            if (_firstWrite)
+            {
+                _firstWrite = false;
+                return _onFirstWriteAsync();
+            }
+            return Task.FromResult(true);
+        }
+
+        // Write with count 0 will still trigger OnFirstWrite
+        public override void Write(byte[] buffer, int offset, int count)
+        {
+            // The Pipe Write method requires calling FlushAsync to notify the reader. Call WriteAsync instead.
+            WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
+        }
+
+        public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            VerifyBuffer(buffer, offset, count, allowEmpty: true);
+            CheckNotComplete();
+
+            await _writeLock.WaitAsync(cancellationToken);
+            try
+            {
+                await FirstWriteAsync();
+                await _pipe.Writer.WriteAsync(new ReadOnlyMemory<byte>(buffer, offset, count), cancellationToken);
+            }
+            finally
+            {
+                _writeLock.Release();
+            }
+        }
+
+        private static void VerifyBuffer(byte[] buffer, int offset, int count, bool allowEmpty)
+        {
+            if (buffer == null)
+            {
+                throw new ArgumentNullException("buffer");
+            }
+            if (offset < 0 || offset > buffer.Length)
+            {
+                throw new ArgumentOutOfRangeException("offset", offset, string.Empty);
+            }
+            if (count < 0 || count > buffer.Length - offset
+                || (!allowEmpty && count == 0))
+            {
+                throw new ArgumentOutOfRangeException("count", count, string.Empty);
+            }
+        }
+
+        internal void Cancel()
+        {
+            _aborted = true;
+            _abortException = new OperationCanceledException();
+            _complete = true;
+            _pipe.Writer.Complete(_abortException);
+        }
+
+        internal void Abort(Exception innerException)
+        {
+            Contract.Requires(innerException != null);
+            _aborted = true;
+            _abortException = innerException;
+            _complete = true;
+            _pipe.Writer.Complete(new IOException(string.Empty, innerException));
+        }
+
+        internal void CompleteWrites()
+        {
+            // If HttpClient.Dispose gets called while HttpClient.SetTask...() is called
+            // there is a chance that this method will be called twice and hang on the lock
+            // to prevent this we can check if there is already a thread inside the lock
+            if (_complete)
+            {
+                return;
+            }
+
+            // Throw for further writes, but not reads.  Allow reads to drain the buffered data and then return 0 for further reads.
+            _complete = true;
+            _pipe.Writer.Complete();
+        }
+
+        private void CheckAborted()
+        {
+            if (_aborted)
+            {
+                throw new IOException(string.Empty, _abortException);
+            }
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                _abortRequest();
+            }
+            base.Dispose(disposing);
+        }
+
+        private void CheckNotComplete()
+        {
+            if (_complete)
+            {
+                throw new IOException("The request was aborted or the pipeline has finished");
+            }
+        }
+    }
+}
diff --git a/src/Hosting/TestHost/src/TestServer.cs b/src/Hosting/TestHost/src/TestServer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..398a575d9df06e38fe0aa6129c56b60c8a47d234
--- /dev/null
+++ b/src/Hosting/TestHost/src/TestServer.cs
@@ -0,0 +1,172 @@
+// 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.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Context = Microsoft.AspNetCore.Hosting.Internal.HostingApplication.Context;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+    public class TestServer : IServer
+    {
+        private const string ServerName = nameof(TestServer);
+        private IWebHost _hostInstance;
+        private bool _disposed = false;
+        private IHttpApplication<Context> _application;
+
+        public TestServer(IWebHostBuilder builder)
+            : this(builder, new FeatureCollection())
+        {
+        }
+
+        public TestServer(IWebHostBuilder builder, IFeatureCollection featureCollection)
+        {
+            if (builder == null)
+            {
+                throw new ArgumentNullException(nameof(builder));
+            }
+            if (featureCollection == null)
+            {
+                throw new ArgumentNullException(nameof(featureCollection));
+            }        
+        
+            Features = featureCollection;
+
+            var host = builder.UseServer(this).Build();
+            host.StartAsync().GetAwaiter().GetResult();
+            _hostInstance = host;
+        }
+
+        public Uri BaseAddress { get; set; } = new Uri("http://localhost/");
+
+        public IWebHost Host
+        {
+            get
+            {
+                return _hostInstance;
+            }
+        }
+
+        public IFeatureCollection Features { get; }
+
+        public HttpMessageHandler CreateHandler()
+        {
+            var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
+            return new ClientHandler(pathBase, _application);
+        }
+
+        public HttpClient CreateClient()
+        {
+            return new HttpClient(CreateHandler()) { BaseAddress = BaseAddress };
+        }
+
+        public WebSocketClient CreateWebSocketClient()
+        {
+            var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
+            return new WebSocketClient(pathBase, _application);
+        }
+
+        /// <summary>
+        /// Begins constructing a request message for submission.
+        /// </summary>
+        /// <param name="path"></param>
+        /// <returns><see cref="RequestBuilder"/> to use in constructing additional request details.</returns>
+        public RequestBuilder CreateRequest(string path)
+        {
+            return new RequestBuilder(this, path);
+        }
+
+        /// <summary>
+        /// Creates, configures, sends, and returns a <see cref="HttpContext"/>. This completes as soon as the response is started.
+        /// </summary>
+        /// <returns></returns>
+        public async Task<HttpContext> SendAsync(Action<HttpContext> configureContext, CancellationToken cancellationToken = default)
+        {
+            if (configureContext == null)
+            {
+                throw new ArgumentNullException(nameof(configureContext));
+            }
+
+            var builder = new HttpContextBuilder(_application);
+            builder.Configure(context =>
+            {
+                var request = context.Request;
+                request.Scheme = BaseAddress.Scheme;
+                request.Host = HostString.FromUriComponent(BaseAddress);
+                if (BaseAddress.IsDefaultPort)
+                {
+                    request.Host = new HostString(request.Host.Host);
+                }
+                var pathBase = PathString.FromUriComponent(BaseAddress);
+                if (pathBase.HasValue && pathBase.Value.EndsWith("/"))
+                {
+                    pathBase = new PathString(pathBase.Value.Substring(0, pathBase.Value.Length - 1));
+                }
+                request.PathBase = pathBase;
+            });
+            builder.Configure(configureContext);
+            return await builder.SendAsync(cancellationToken).ConfigureAwait(false);
+        }
+
+        public void Dispose()
+        {
+            if (!_disposed)
+            {
+                _disposed = true;
+                _hostInstance.Dispose();
+            }
+        }
+
+        Task IServer.StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
+        {
+            _application = new ApplicationWrapper<Context>((IHttpApplication<Context>)application, () =>
+            {
+                if (_disposed)
+                {
+                    throw new ObjectDisposedException(GetType().FullName);
+                }
+            });
+
+            return Task.CompletedTask;
+        }
+
+        Task IServer.StopAsync(CancellationToken cancellationToken)
+        {
+            return Task.CompletedTask;
+        }
+
+        private class ApplicationWrapper<TContext> : IHttpApplication<TContext>
+        {
+            private readonly IHttpApplication<TContext> _application;
+            private readonly Action _preProcessRequestAsync;
+
+            public ApplicationWrapper(IHttpApplication<TContext> application, Action preProcessRequestAsync)
+            {
+                _application = application;
+                _preProcessRequestAsync = preProcessRequestAsync;
+            }
+
+            public TContext CreateContext(IFeatureCollection contextFeatures)
+            {
+                return _application.CreateContext(contextFeatures);
+            }
+
+            public void DisposeContext(TContext context, Exception exception)
+            {
+                _application.DisposeContext(context, exception);
+            }
+
+            public Task ProcessRequestAsync(TContext context)
+            {
+                _preProcessRequestAsync();
+                return _application.ProcessRequestAsync(context);
+            }
+        }
+    }
+}
diff --git a/src/Hosting/TestHost/src/TestWebSocket.cs b/src/Hosting/TestHost/src/TestWebSocket.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d1a77e5af6634d67628b8dbf716de14f4d3537d8
--- /dev/null
+++ b/src/Hosting/TestHost/src/TestWebSocket.cs
@@ -0,0 +1,354 @@
+// 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.Net.WebSockets;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+    internal class TestWebSocket : WebSocket
+    {
+        private ReceiverSenderBuffer _receiveBuffer;
+        private ReceiverSenderBuffer _sendBuffer;
+        private readonly string _subProtocol;
+        private WebSocketState _state;
+        private WebSocketCloseStatus? _closeStatus;
+        private string _closeStatusDescription;
+        private Message _receiveMessage;
+
+        public static Tuple<TestWebSocket, TestWebSocket> CreatePair(string subProtocol)
+        {
+            var buffers = new[] { new ReceiverSenderBuffer(), new ReceiverSenderBuffer() };
+            return Tuple.Create(
+                new TestWebSocket(subProtocol, buffers[0], buffers[1]),
+                new TestWebSocket(subProtocol, buffers[1], buffers[0]));
+        }
+
+        public override WebSocketCloseStatus? CloseStatus
+        {
+            get { return _closeStatus; }
+        }
+
+        public override string CloseStatusDescription
+        {
+            get { return _closeStatusDescription; }
+        }
+
+        public override WebSocketState State
+        {
+            get { return _state; }
+        }
+
+        public override string SubProtocol
+        {
+            get { return _subProtocol; }
+        }
+
+        public async override Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
+        {
+            ThrowIfDisposed();
+
+            if (State == WebSocketState.Open || State == WebSocketState.CloseReceived)
+            {
+                // Send a close message.
+                await CloseOutputAsync(closeStatus, statusDescription, cancellationToken);
+            }
+
+            if (State == WebSocketState.CloseSent)
+            {
+                // Do a receiving drain
+                var data = new byte[1024];
+                WebSocketReceiveResult result;
+                do
+                {
+                    result = await ReceiveAsync(new ArraySegment<byte>(data), cancellationToken);
+                }
+                while (result.MessageType != WebSocketMessageType.Close);
+            }
+        }
+
+        public async override Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
+        {
+            ThrowIfDisposed();
+            ThrowIfOutputClosed();
+
+            var message = new Message(closeStatus, statusDescription);
+            await _sendBuffer.SendAsync(message, cancellationToken);
+
+            if (State == WebSocketState.Open)
+            {
+                _state = WebSocketState.CloseSent;
+            }
+            else if (State == WebSocketState.CloseReceived)
+            {
+                _state = WebSocketState.Closed;
+                Close();
+            }
+        }
+
+        public override void Abort()
+        {
+            if (_state >= WebSocketState.Closed) // or Aborted
+            {
+                return;
+            }
+
+            _state = WebSocketState.Aborted;
+            Close();
+        }
+
+        public override void Dispose()
+        {
+            if (_state >= WebSocketState.Closed) // or Aborted
+            {
+                return;
+            }
+
+            _state = WebSocketState.Closed;
+            Close();
+        }
+
+        public override async Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken)
+        {
+            ThrowIfDisposed();
+            ThrowIfInputClosed();
+            ValidateSegment(buffer);
+            // TODO: InvalidOperationException if any receives are currently in progress.
+            
+            Message receiveMessage = _receiveMessage;
+            _receiveMessage = null;
+            if (receiveMessage == null)
+            {
+                receiveMessage = await _receiveBuffer.ReceiveAsync(cancellationToken);
+            }
+            if (receiveMessage.MessageType == WebSocketMessageType.Close)
+            {
+                _closeStatus = receiveMessage.CloseStatus;
+                _closeStatusDescription = receiveMessage.CloseStatusDescription ?? string.Empty;
+                var result = new WebSocketReceiveResult(0, WebSocketMessageType.Close, true, _closeStatus, _closeStatusDescription);
+                if (_state == WebSocketState.Open)
+                {
+                    _state = WebSocketState.CloseReceived;
+                }
+                else if (_state == WebSocketState.CloseSent)
+                {
+                    _state = WebSocketState.Closed;
+                    Close();
+                }
+                return result;
+            }
+            else
+            {
+                int count = Math.Min(buffer.Count, receiveMessage.Buffer.Count);
+                bool endOfMessage = count == receiveMessage.Buffer.Count;
+                Array.Copy(receiveMessage.Buffer.Array, receiveMessage.Buffer.Offset, buffer.Array, buffer.Offset, count);
+                if (!endOfMessage)
+                {
+                    receiveMessage.Buffer = new ArraySegment<byte>(receiveMessage.Buffer.Array, receiveMessage.Buffer.Offset + count, receiveMessage.Buffer.Count - count);
+                    _receiveMessage = receiveMessage;
+                }
+                endOfMessage = endOfMessage && receiveMessage.EndOfMessage;
+                return new WebSocketReceiveResult(count, receiveMessage.MessageType, endOfMessage);
+            }
+        }
+
+        public override Task SendAsync(ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken)
+        {
+            ValidateSegment(buffer);
+            if (messageType != WebSocketMessageType.Binary && messageType != WebSocketMessageType.Text)
+            {
+                // Block control frames
+                throw new ArgumentOutOfRangeException(nameof(messageType), messageType, string.Empty);
+            }
+
+            var message = new Message(buffer, messageType, endOfMessage, cancellationToken);
+            return _sendBuffer.SendAsync(message, cancellationToken);
+        }
+
+        private void Close()
+        {
+            _receiveBuffer.SetReceiverClosed();
+            _sendBuffer.SetSenderClosed();
+        }
+
+        private void ThrowIfDisposed()
+        {
+            if (_state >= WebSocketState.Closed) // or Aborted
+            {
+                throw new ObjectDisposedException(typeof(TestWebSocket).FullName);
+            }
+        }
+
+        private void ThrowIfOutputClosed()
+        {
+            if (State == WebSocketState.CloseSent)
+            {
+                throw new InvalidOperationException("Close already sent.");
+            }
+        }
+
+        private void ThrowIfInputClosed()
+        {
+            if (State == WebSocketState.CloseReceived)
+            {
+                throw new InvalidOperationException("Close already received.");
+            }
+        }
+
+        private void ValidateSegment(ArraySegment<byte> buffer)
+        {
+            if (buffer.Array == null)
+            {
+                throw new ArgumentNullException(nameof(buffer));
+            }
+            if (buffer.Offset < 0 || buffer.Offset > buffer.Array.Length)
+            {
+                throw new ArgumentOutOfRangeException(nameof(buffer.Offset), buffer.Offset, string.Empty);
+            }
+            if (buffer.Count < 0 || buffer.Count > buffer.Array.Length - buffer.Offset)
+            {
+                throw new ArgumentOutOfRangeException(nameof(buffer.Count), buffer.Count, string.Empty);
+            }
+        }
+
+        private TestWebSocket(string subProtocol, ReceiverSenderBuffer readBuffer, ReceiverSenderBuffer writeBuffer)
+        {
+            _state = WebSocketState.Open;
+            _subProtocol = subProtocol;
+            _receiveBuffer = readBuffer;
+            _sendBuffer = writeBuffer;
+        }
+
+        private class Message
+        {
+            public Message(ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken token)
+            {
+                Buffer = buffer;
+                CloseStatus = null;
+                CloseStatusDescription = null;
+                EndOfMessage = endOfMessage;
+                MessageType = messageType;
+            }
+
+            public Message(WebSocketCloseStatus? closeStatus, string closeStatusDescription)
+            {
+                Buffer = new ArraySegment<byte>(new byte[0]);
+                CloseStatus = closeStatus;
+                CloseStatusDescription = closeStatusDescription;
+                MessageType = WebSocketMessageType.Close;
+                EndOfMessage = true;
+            }
+
+            public WebSocketCloseStatus? CloseStatus { get; set; }
+            public string CloseStatusDescription { get; set; }
+            public ArraySegment<byte> Buffer { get; set; }
+            public bool EndOfMessage { get; set; }
+            public WebSocketMessageType MessageType { get; set; }
+        }
+
+        private class ReceiverSenderBuffer
+        {
+            private bool _receiverClosed;
+            private bool _senderClosed;
+            private bool _disposed;
+            private SemaphoreSlim _sem;
+            private Queue<Message> _messageQueue;
+            
+            public ReceiverSenderBuffer()
+            {
+                _sem = new SemaphoreSlim(0);
+                _messageQueue = new Queue<Message>();
+            }
+
+            public async virtual Task<Message> ReceiveAsync(CancellationToken cancellationToken)
+            {
+                if (_disposed)
+                {
+                    ThrowNoReceive();
+                }
+                await _sem.WaitAsync(cancellationToken);
+                lock (_messageQueue)
+                {
+                    if (_messageQueue.Count == 0)
+                    {
+                        _disposed = true;
+                        _sem.Dispose();
+                        ThrowNoReceive();
+                    }
+                    return _messageQueue.Dequeue();
+                }
+            }
+
+            public virtual Task SendAsync(Message message, CancellationToken cancellationToken)
+            {
+                lock (_messageQueue)
+                {
+                    if (_senderClosed)
+                    {
+                        throw new ObjectDisposedException(typeof(TestWebSocket).FullName);
+                    }
+                    if (_receiverClosed)
+                    {
+                        throw new IOException("The remote end closed the connection.", new ObjectDisposedException(typeof(TestWebSocket).FullName));
+                    }
+
+                    // we return immediately so we need to copy the buffer since the sender can re-use it
+                    var array = new byte[message.Buffer.Count];
+                    Array.Copy(message.Buffer.Array, message.Buffer.Offset, array, 0, message.Buffer.Count);
+                    message.Buffer = new ArraySegment<byte>(array);
+
+                    _messageQueue.Enqueue(message);
+                    _sem.Release();
+
+                    return Task.FromResult(true);
+                }
+            }
+
+            public void SetReceiverClosed()
+            {
+                lock (_messageQueue)
+                {
+                    if (!_receiverClosed)
+                    {
+                        _receiverClosed = true;
+                        if (!_disposed)
+                        {
+                            _sem.Release();
+                        }
+                    }
+                }
+            }
+
+            public void SetSenderClosed()
+            {
+                lock (_messageQueue)
+                {
+                    if (!_senderClosed)
+                    {
+                        _senderClosed = true;
+                        if (!_disposed)
+                        {
+                            _sem.Release();
+                        }
+                    }
+                }
+            }
+
+            private void ThrowNoReceive()
+            {
+                if (_receiverClosed)
+                {
+                    throw new ObjectDisposedException(typeof(TestWebSocket).FullName);
+                }
+                else // _senderClosed
+                {
+                    throw new IOException("The remote end closed the connection.", new ObjectDisposedException(typeof(TestWebSocket).FullName));
+                }
+            }
+        }
+    }
+}
diff --git a/src/Hosting/TestHost/src/WebHostBuilderExtensions.cs b/src/Hosting/TestHost/src/WebHostBuilderExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..cccf19cdd5ec7a721c85152a26a9b2d631b50a8f
--- /dev/null
+++ b/src/Hosting/TestHost/src/WebHostBuilderExtensions.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.IO;
+using System.Linq;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting.Internal;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+    public static class WebHostBuilderExtensions
+    {
+        public static IWebHostBuilder ConfigureTestServices(this IWebHostBuilder webHostBuilder, Action<IServiceCollection> servicesConfiguration)
+        {
+            if (webHostBuilder == null)
+            {
+                throw new ArgumentNullException(nameof(webHostBuilder));
+            }
+
+            if (servicesConfiguration == null)
+            {
+                throw new ArgumentNullException(nameof(servicesConfiguration));
+            }
+
+            webHostBuilder.ConfigureServices(
+                s => s.AddSingleton<IStartupConfigureServicesFilter>(
+                    new ConfigureTestServicesStartupConfigureServicesFilter(servicesConfiguration)));
+
+            return webHostBuilder;
+        }
+
+        public static IWebHostBuilder ConfigureTestContainer<TContainer>(this IWebHostBuilder webHostBuilder, Action<TContainer> servicesConfiguration)
+        {
+            if (webHostBuilder == null)
+            {
+                throw new ArgumentNullException(nameof(webHostBuilder));
+            }
+
+            if (servicesConfiguration == null)
+            {
+                throw new ArgumentNullException(nameof(servicesConfiguration));
+            }
+
+            webHostBuilder.ConfigureServices(
+                s => s.AddSingleton<IStartupConfigureContainerFilter<TContainer>>(
+                    new ConfigureTestServicesStartupConfigureContainerFilter<TContainer>(servicesConfiguration)));
+
+            return webHostBuilder;
+        }
+
+        public static IWebHostBuilder UseSolutionRelativeContentRoot(
+            this IWebHostBuilder builder,
+            string solutionRelativePath,
+            string solutionName = "*.sln")
+        {
+            return builder.UseSolutionRelativeContentRoot(solutionRelativePath, AppContext.BaseDirectory, solutionName);
+        }
+
+        public static IWebHostBuilder UseSolutionRelativeContentRoot(
+            this IWebHostBuilder builder,
+            string solutionRelativePath,
+            string applicationBasePath,
+            string solutionName = "*.sln")
+        {
+            if (solutionRelativePath == null)
+            {
+                throw new ArgumentNullException(nameof(solutionRelativePath));
+            }
+
+            if (applicationBasePath == null)
+            {
+                throw new ArgumentNullException(nameof(applicationBasePath));
+            }
+
+            var directoryInfo = new DirectoryInfo(applicationBasePath);
+            do
+            {
+                var solutionPath = Directory.EnumerateFiles(directoryInfo.FullName, solutionName).FirstOrDefault();
+                if (solutionPath != null)
+                {
+                    builder.UseContentRoot(Path.GetFullPath(Path.Combine(directoryInfo.FullName, solutionRelativePath)));
+                    return builder;
+                }
+
+                directoryInfo = directoryInfo.Parent;
+            }
+            while (directoryInfo.Parent != null);
+
+            throw new InvalidOperationException($"Solution root could not be located using application root {applicationBasePath}.");
+        }
+
+        private class ConfigureTestServicesStartupConfigureServicesFilter : IStartupConfigureServicesFilter
+        {
+            private readonly Action<IServiceCollection> _servicesConfiguration;
+
+            public ConfigureTestServicesStartupConfigureServicesFilter(Action<IServiceCollection> servicesConfiguration)
+            {
+                if (servicesConfiguration == null)
+                {
+                    throw new ArgumentNullException(nameof(servicesConfiguration));
+                }
+
+                _servicesConfiguration = servicesConfiguration;
+            }
+
+            public Action<IServiceCollection> ConfigureServices(Action<IServiceCollection> next) =>
+                serviceCollection =>
+                {
+                    next(serviceCollection);
+                    _servicesConfiguration(serviceCollection);
+                };
+        }
+
+        private class ConfigureTestServicesStartupConfigureContainerFilter<TContainer> : IStartupConfigureContainerFilter<TContainer>
+        {
+            private readonly Action<TContainer> _servicesConfiguration;
+
+            public ConfigureTestServicesStartupConfigureContainerFilter(Action<TContainer> containerConfiguration)
+            {
+                if (containerConfiguration == null)
+                {
+                    throw new ArgumentNullException(nameof(containerConfiguration));
+                }
+
+                _servicesConfiguration = containerConfiguration;
+            }
+
+            public Action<TContainer> ConfigureContainer(Action<TContainer> next) =>
+                containerBuilder =>
+                {
+                    next(containerBuilder);
+                    _servicesConfiguration(containerBuilder);
+                };
+        }
+    }
+}
diff --git a/src/Hosting/TestHost/src/WebHostBuilderFactory.cs b/src/Hosting/TestHost/src/WebHostBuilderFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3a0b0a552e9b60da883a4948bb3a0ee9d9b3e2dd
--- /dev/null
+++ b/src/Hosting/TestHost/src/WebHostBuilderFactory.cs
@@ -0,0 +1,26 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Reflection;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting.WebHostBuilderFactory;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+    public static class WebHostBuilderFactory
+    {
+        public static IWebHostBuilder CreateFromAssemblyEntryPoint(Assembly assembly, string [] args)
+        {
+            var result = WebHostFactoryResolver.ResolveWebHostBuilderFactory<IWebHost,IWebHostBuilder>(assembly);
+            if (result.ResultKind != FactoryResolutionResultKind.Success)
+            {
+                return null;
+            }
+
+            return result.WebHostBuilderFactory(args);
+        }
+
+        public static IWebHostBuilder CreateFromTypesAssemblyEntryPoint<T>(string[] args) =>
+            CreateFromAssemblyEntryPoint(typeof(T).Assembly, args);
+    }
+}
diff --git a/src/Hosting/TestHost/src/WebSocketClient.cs b/src/Hosting/TestHost/src/WebSocketClient.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e3deb670a5a2fb5b4d0ad6b0f8a26181a549aae5
--- /dev/null
+++ b/src/Hosting/TestHost/src/WebSocketClient.cs
@@ -0,0 +1,134 @@
+// 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.Net.WebSockets;
+using System.Security.Cryptography;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Context = Microsoft.AspNetCore.Hosting.Internal.HostingApplication.Context;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+    public class WebSocketClient
+    {
+        private readonly IHttpApplication<Context> _application;
+        private readonly PathString _pathBase;
+
+        internal WebSocketClient(PathString pathBase, IHttpApplication<Context> application)
+        {
+            _application = application ?? throw new ArgumentNullException(nameof(application));
+
+            // PathString.StartsWithSegments that we use below requires the base path to not end in a slash.
+            if (pathBase.HasValue && pathBase.Value.EndsWith("/"))
+            {
+                pathBase = new PathString(pathBase.Value.Substring(0, pathBase.Value.Length - 1));
+            }
+            _pathBase = pathBase;
+
+            SubProtocols = new List<string>();
+        }
+
+        public IList<string> SubProtocols
+        {
+            get;
+            private set;
+        }
+
+        public Action<HttpRequest> ConfigureRequest
+        {
+            get;
+            set;
+        }
+
+        public async Task<WebSocket> ConnectAsync(Uri uri, CancellationToken cancellationToken)
+        {
+            WebSocketFeature webSocketFeature = null;
+            var contextBuilder = new HttpContextBuilder(_application);
+            contextBuilder.Configure(context =>
+            {
+                var request = context.Request;
+                var scheme = uri.Scheme;
+                scheme = (scheme == "ws") ? "http" : scheme;
+                scheme = (scheme == "wss") ? "https" : scheme;
+                request.Scheme = scheme;
+                request.Path = PathString.FromUriComponent(uri);
+                request.PathBase = PathString.Empty;
+                if (request.Path.StartsWithSegments(_pathBase, out var remainder))
+                {
+                    request.Path = remainder;
+                    request.PathBase = _pathBase;
+                }
+                request.QueryString = QueryString.FromUriComponent(uri);
+                request.Headers.Add("Connection", new string[] { "Upgrade" });
+                request.Headers.Add("Upgrade", new string[] { "websocket" });
+                request.Headers.Add("Sec-WebSocket-Version", new string[] { "13" });
+                request.Headers.Add("Sec-WebSocket-Key", new string[] { CreateRequestKey() });
+                request.Body = Stream.Null;
+
+                // WebSocket
+                webSocketFeature = new WebSocketFeature(context);
+                context.Features.Set<IHttpWebSocketFeature>(webSocketFeature);
+
+                ConfigureRequest?.Invoke(context.Request);
+            });
+
+            var httpContext = await contextBuilder.SendAsync(cancellationToken);
+
+            if (httpContext.Response.StatusCode != StatusCodes.Status101SwitchingProtocols)
+            {
+                throw new InvalidOperationException("Incomplete handshake, status code: " + httpContext.Response.StatusCode);
+            }
+            if (webSocketFeature.ClientWebSocket == null)
+            {
+                throw new InvalidOperationException("Incomplete handshake");
+            }
+
+            return webSocketFeature.ClientWebSocket;
+        }
+
+        private string CreateRequestKey()
+        {
+            byte[] data = new byte[16];
+            var rng = RandomNumberGenerator.Create();
+            rng.GetBytes(data);
+            return Convert.ToBase64String(data);
+        }
+
+        private class WebSocketFeature : IHttpWebSocketFeature
+        {
+            private readonly HttpContext _httpContext;
+
+            public WebSocketFeature(HttpContext context)
+            {
+                _httpContext = context;
+            }
+
+            bool IHttpWebSocketFeature.IsWebSocketRequest => true;
+
+            public WebSocket ClientWebSocket { get; private set; }
+
+            public WebSocket ServerWebSocket { get; private set; }
+
+            async Task<WebSocket> IHttpWebSocketFeature.AcceptAsync(WebSocketAcceptContext context)
+            {
+                var websockets = TestWebSocket.CreatePair(context.SubProtocol);
+                if (_httpContext.Response.HasStarted)
+                {
+                    throw new InvalidOperationException("The response has already started");
+                }
+
+                _httpContext.Response.StatusCode = StatusCodes.Status101SwitchingProtocols;
+                ClientWebSocket = websockets.Item1;
+                ServerWebSocket = websockets.Item2;
+                await _httpContext.Response.Body.FlushAsync(_httpContext.RequestAborted); // Send headers to the client
+                return ServerWebSocket;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/TestHost/src/baseline.netcore.json b/src/Hosting/TestHost/src/baseline.netcore.json
new file mode 100644
index 0000000000000000000000000000000000000000..85f67361ddca97cfd3bb0756ddebf96d733adfde
--- /dev/null
+++ b/src/Hosting/TestHost/src/baseline.netcore.json
@@ -0,0 +1,316 @@
+{
+  "AssemblyIdentity": "Microsoft.AspNetCore.TestHost, Version=2.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+  "Types": [
+    {
+      "Name": "Microsoft.AspNetCore.TestHost.ClientHandler",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "BaseType": "System.Net.Http.HttpMessageHandler",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "SendAsync",
+          "Parameters": [
+            {
+              "Name": "request",
+              "Type": "System.Net.Http.HttpRequestMessage"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage>",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "pathBase",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            },
+            {
+              "Name": "application",
+              "Type": "Microsoft.AspNetCore.Hosting.Server.IHttpApplication<Microsoft.AspNetCore.Hosting.Internal.HostingApplication+Context>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.TestHost.RequestBuilder",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "And",
+          "Parameters": [
+            {
+              "Name": "configure",
+              "Type": "System.Action<System.Net.Http.HttpRequestMessage>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.TestHost.RequestBuilder",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AddHeader",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.TestHost.RequestBuilder",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SendAsync",
+          "Parameters": [
+            {
+              "Name": "method",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "PostAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<System.Net.Http.HttpResponseMessage>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "server",
+              "Type": "Microsoft.AspNetCore.TestHost.TestServer"
+            },
+            {
+              "Name": "path",
+              "Type": "System.String"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.TestHost.TestServer",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Hosting.Server.IServer"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_BaseAddress",
+          "Parameters": [],
+          "ReturnType": "System.Uri",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_BaseAddress",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Uri"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Host",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHost",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Features",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Hosting.Server.IServer",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "CreateHandler",
+          "Parameters": [],
+          "ReturnType": "System.Net.Http.HttpMessageHandler",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "CreateClient",
+          "Parameters": [],
+          "ReturnType": "System.Net.Http.HttpClient",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "CreateWebSocketClient",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.TestHost.WebSocketClient",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "CreateRequest",
+          "Parameters": [
+            {
+              "Name": "path",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.TestHost.RequestBuilder",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Dispose",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.IDisposable",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "builder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "builder",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder"
+            },
+            {
+              "Name": "featureCollection",
+              "Type": "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.TestHost.WebSocketClient",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_SubProtocols",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IList<System.String>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ConfigureRequest",
+          "Parameters": [],
+          "ReturnType": "System.Action<Microsoft.AspNetCore.Http.HttpRequest>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ConfigureRequest",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Action<Microsoft.AspNetCore.Http.HttpRequest>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ConnectAsync",
+          "Parameters": [
+            {
+              "Name": "uri",
+              "Type": "System.Uri"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Net.WebSockets.WebSocket>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/Hosting/TestHost/test/ClientHandlerTests.cs b/src/Hosting/TestHost/test/ClientHandlerTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..73f1c86d2973bc2e9c958ae9eb461ab6a72e430a
--- /dev/null
+++ b/src/Hosting/TestHost/test/ClientHandlerTests.cs
@@ -0,0 +1,372 @@
+// 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.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Testing.xunit;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Xunit;
+using Context = Microsoft.AspNetCore.Hosting.Internal.HostingApplication.Context;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+    public class ClientHandlerTests
+    {
+        [Fact]
+        public Task ExpectedKeysAreAvailable()
+        {
+            var handler = new ClientHandler(new PathString("/A/Path/"), new DummyApplication(context =>
+            {
+                // TODO: Assert.True(context.RequestAborted.CanBeCanceled);
+#if NETCOREAPP2_1
+                Assert.Equal("HTTP/2.0", context.Request.Protocol);
+#elif NET461 || NETCOREAPP2_0
+                Assert.Equal("HTTP/1.1", context.Request.Protocol);
+#else
+    Unspecified Framework
+#endif
+                Assert.Equal("GET", context.Request.Method);
+                Assert.Equal("https", context.Request.Scheme);
+                Assert.Equal("/A/Path", context.Request.PathBase.Value);
+                Assert.Equal("/and/file.txt", context.Request.Path.Value);
+                Assert.Equal("?and=query", context.Request.QueryString.Value);
+                Assert.NotNull(context.Request.Body);
+                Assert.NotNull(context.Request.Headers);
+                Assert.NotNull(context.Response.Headers);
+                Assert.NotNull(context.Response.Body);
+                Assert.Equal(200, context.Response.StatusCode);
+                Assert.Null(context.Features.Get<IHttpResponseFeature>().ReasonPhrase);
+                Assert.Equal("example.com", context.Request.Host.Value);
+
+                return Task.FromResult(0);
+            }));
+            var httpClient = new HttpClient(handler);
+            return httpClient.GetAsync("https://example.com/A/Path/and/file.txt?and=query");
+        }
+
+        [Fact]
+        public Task ExpectedKeysAreInFeatures()
+        {
+            var handler = new ClientHandler(new PathString("/A/Path/"), new InspectingApplication(features =>
+            {
+                // TODO: Assert.True(context.RequestAborted.CanBeCanceled);
+#if NETCOREAPP2_1
+                Assert.Equal("HTTP/2.0", features.Get<IHttpRequestFeature>().Protocol);
+#elif NET461 || NETCOREAPP2_0
+                Assert.Equal("HTTP/1.1", features.Get<IHttpRequestFeature>().Protocol);
+#else
+    Unspecified Framework
+#endif
+                Assert.Equal("GET", features.Get<IHttpRequestFeature>().Method);
+                Assert.Equal("https", features.Get<IHttpRequestFeature>().Scheme);
+                Assert.Equal("/A/Path", features.Get<IHttpRequestFeature>().PathBase);
+                Assert.Equal("/and/file.txt", features.Get<IHttpRequestFeature>().Path);
+                Assert.Equal("?and=query", features.Get<IHttpRequestFeature>().QueryString);
+                Assert.NotNull(features.Get<IHttpRequestFeature>().Body);
+                Assert.NotNull(features.Get<IHttpRequestFeature>().Headers);
+                Assert.NotNull(features.Get<IHttpResponseFeature>().Headers);
+                Assert.NotNull(features.Get<IHttpResponseFeature>().Body);
+                Assert.Equal(200, features.Get<IHttpResponseFeature>().StatusCode);
+                Assert.Null(features.Get<IHttpResponseFeature>().ReasonPhrase);
+                Assert.Equal("example.com", features.Get<IHttpRequestFeature>().Headers["host"]);
+                Assert.NotNull(features.Get<IHttpRequestLifetimeFeature>());
+            }));
+            var httpClient = new HttpClient(handler);
+            return httpClient.GetAsync("https://example.com/A/Path/and/file.txt?and=query");
+        }
+
+        [Fact]
+        public Task SingleSlashNotMovedToPathBase()
+        {
+            var handler = new ClientHandler(new PathString(""), new DummyApplication(context =>
+            {
+                Assert.Equal("", context.Request.PathBase.Value);
+                Assert.Equal("/", context.Request.Path.Value);
+
+                return Task.FromResult(0);
+            }));
+            var httpClient = new HttpClient(handler);
+            return httpClient.GetAsync("https://example.com/");
+        }
+
+        [Fact]
+        public async Task ResubmitRequestWorks()
+        {
+            int requestCount = 1;
+            var handler = new ClientHandler(PathString.Empty, new DummyApplication(context =>
+            {
+                int read = context.Request.Body.Read(new byte[100], 0, 100);
+                Assert.Equal(11, read);
+
+                context.Response.Headers["TestHeader"] = "TestValue:" + requestCount++;
+                return Task.FromResult(0);
+            }));
+
+            HttpMessageInvoker invoker = new HttpMessageInvoker(handler);
+            HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, "https://example.com/");
+            message.Content = new StringContent("Hello World");
+
+            HttpResponseMessage response = await invoker.SendAsync(message, CancellationToken.None);
+            Assert.Equal("TestValue:1", response.Headers.GetValues("TestHeader").First());
+
+            response = await invoker.SendAsync(message, CancellationToken.None);
+            Assert.Equal("TestValue:2", response.Headers.GetValues("TestHeader").First());
+        }
+
+        [Fact]
+        public async Task MiddlewareOnlySetsHeaders()
+        {
+            var handler = new ClientHandler(PathString.Empty, new DummyApplication(context =>
+            {
+                context.Response.Headers["TestHeader"] = "TestValue";
+                return Task.FromResult(0);
+            }));
+            var httpClient = new HttpClient(handler);
+            HttpResponseMessage response = await httpClient.GetAsync("https://example.com/");
+            Assert.Equal("TestValue", response.Headers.GetValues("TestHeader").First());
+        }
+
+        [Fact]
+        public async Task BlockingMiddlewareShouldNotBlockClient()
+        {
+            ManualResetEvent block = new ManualResetEvent(false);
+            var handler = new ClientHandler(PathString.Empty, new DummyApplication(context =>
+            {
+                block.WaitOne();
+                return Task.FromResult(0);
+            }));
+            var httpClient = new HttpClient(handler);
+            Task<HttpResponseMessage> task = httpClient.GetAsync("https://example.com/");
+            Assert.False(task.IsCompleted);
+            Assert.False(task.Wait(50));
+            block.Set();
+            HttpResponseMessage response = await task;
+        }
+
+        [Fact]
+        public async Task HeadersAvailableBeforeBodyFinished()
+        {
+            ManualResetEvent block = new ManualResetEvent(false);
+            var handler = new ClientHandler(PathString.Empty, new DummyApplication(async context =>
+            {
+                context.Response.Headers["TestHeader"] = "TestValue";
+                await context.Response.WriteAsync("BodyStarted,");
+                block.WaitOne();
+                await context.Response.WriteAsync("BodyFinished");
+            }));
+            var httpClient = new HttpClient(handler);
+            HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
+                HttpCompletionOption.ResponseHeadersRead);
+            Assert.Equal("TestValue", response.Headers.GetValues("TestHeader").First());
+            block.Set();
+            Assert.Equal("BodyStarted,BodyFinished", await response.Content.ReadAsStringAsync());
+        }
+
+        [Fact]
+        public async Task FlushSendsHeaders()
+        {
+            ManualResetEvent block = new ManualResetEvent(false);
+            var handler = new ClientHandler(PathString.Empty, new DummyApplication(async context =>
+            {
+                context.Response.Headers["TestHeader"] = "TestValue";
+                context.Response.Body.Flush();
+                block.WaitOne();
+                await context.Response.WriteAsync("BodyFinished");
+            }));
+            var httpClient = new HttpClient(handler);
+            HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
+                HttpCompletionOption.ResponseHeadersRead);
+            Assert.Equal("TestValue", response.Headers.GetValues("TestHeader").First());
+            block.Set();
+            Assert.Equal("BodyFinished", await response.Content.ReadAsStringAsync());
+        }
+
+        [Fact]
+        public async Task ClientDisposalCloses()
+        {
+            ManualResetEvent block = new ManualResetEvent(false);
+            var handler = new ClientHandler(PathString.Empty, new DummyApplication(context =>
+            {
+                context.Response.Headers["TestHeader"] = "TestValue";
+                context.Response.Body.Flush();
+                block.WaitOne();
+                return Task.FromResult(0);
+            }));
+            var httpClient = new HttpClient(handler);
+            HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
+                HttpCompletionOption.ResponseHeadersRead);
+            Assert.Equal("TestValue", response.Headers.GetValues("TestHeader").First());
+            Stream responseStream = await response.Content.ReadAsStreamAsync();
+            Task<int> readTask = responseStream.ReadAsync(new byte[100], 0, 100);
+            Assert.False(readTask.IsCompleted);
+            responseStream.Dispose();
+            Assert.True(readTask.Wait(TimeSpan.FromSeconds(10)), "Finished");
+            Assert.Equal(0, readTask.Result);
+            block.Set();
+        }
+
+        [Fact]
+        public async Task ClientCancellationAborts()
+        {
+            ManualResetEvent block = new ManualResetEvent(false);
+            var handler = new ClientHandler(PathString.Empty, new DummyApplication(context =>
+            {
+                context.Response.Headers["TestHeader"] = "TestValue";
+                context.Response.Body.Flush();
+                block.WaitOne();
+                return Task.FromResult(0);
+            }));
+            var httpClient = new HttpClient(handler);
+            HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
+                HttpCompletionOption.ResponseHeadersRead);
+            Assert.Equal("TestValue", response.Headers.GetValues("TestHeader").First());
+            Stream responseStream = await response.Content.ReadAsStreamAsync();
+            CancellationTokenSource cts = new CancellationTokenSource();
+            Task<int> readTask = responseStream.ReadAsync(new byte[100], 0, 100, cts.Token);
+            Assert.False(readTask.IsCompleted, "Not Completed");
+            cts.Cancel();
+            var ex = Assert.Throws<AggregateException>(() => readTask.Wait(TimeSpan.FromSeconds(10)));
+            Assert.IsAssignableFrom<OperationCanceledException>(ex.GetBaseException());
+            block.Set();
+        }
+
+        [Fact]
+        public Task ExceptionBeforeFirstWriteIsReported()
+        {
+            var handler = new ClientHandler(PathString.Empty, new DummyApplication(context =>
+            {
+                throw new InvalidOperationException("Test Exception");
+            }));
+            var httpClient = new HttpClient(handler);
+            return Assert.ThrowsAsync<InvalidOperationException>(() => httpClient.GetAsync("https://example.com/",
+                HttpCompletionOption.ResponseHeadersRead));
+        }
+
+        [Fact]
+        public async Task ExceptionAfterFirstWriteIsReported()
+        {
+            ManualResetEvent block = new ManualResetEvent(false);
+            var handler = new ClientHandler(PathString.Empty, new DummyApplication(async context =>
+            {
+                context.Response.Headers["TestHeader"] = "TestValue";
+                await context.Response.WriteAsync("BodyStarted");
+                block.WaitOne();
+                throw new InvalidOperationException("Test Exception");
+            }));
+            var httpClient = new HttpClient(handler);
+            HttpResponseMessage response = await httpClient.GetAsync("https://example.com/",
+                HttpCompletionOption.ResponseHeadersRead);
+            Assert.Equal("TestValue", response.Headers.GetValues("TestHeader").First());
+            block.Set();
+            var ex = await Assert.ThrowsAsync<HttpRequestException>(() => response.Content.ReadAsStringAsync());
+            Assert.IsType<InvalidOperationException>(ex.GetBaseException());
+        }
+
+        private class DummyApplication : IHttpApplication<Context>
+        {
+            RequestDelegate _application;
+
+            public DummyApplication(RequestDelegate application)
+            {
+                _application = application;
+            }
+
+            public Context CreateContext(IFeatureCollection contextFeatures)
+            {
+                return new Context()
+                {
+                    HttpContext = new DefaultHttpContext(contextFeatures)
+                };
+            }
+
+            public void DisposeContext(Context context, Exception exception)
+            {
+
+            }
+
+            public Task ProcessRequestAsync(Context context)
+            {
+                return _application(context.HttpContext);
+            }
+        }
+
+        private class InspectingApplication : IHttpApplication<Context>
+        {
+            Action<IFeatureCollection> _inspector;
+
+            public InspectingApplication(Action<IFeatureCollection> inspector)
+            {
+                _inspector = inspector;
+            }
+
+            public Context CreateContext(IFeatureCollection contextFeatures)
+            {
+                _inspector(contextFeatures);
+                return new Context()
+                {
+                    HttpContext = new DefaultHttpContext(contextFeatures)
+                };
+            }
+
+            public void DisposeContext(Context context, Exception exception)
+            {
+
+            }
+
+            public Task ProcessRequestAsync(Context context)
+            {
+                return Task.FromResult(0);
+            }
+        }
+        
+        [Fact]
+        public async Task ClientHandlerCreateContextWithDefaultRequestParameters()
+        {
+            // This logger will attempt to access information from HttpRequest once the HttpContext is created
+            var logger = new VerifierLogger();
+            var builder = new WebHostBuilder()
+                .ConfigureServices(services =>
+                {
+                    services.AddSingleton<ILogger<IWebHost>>(logger);
+                })
+                .Configure(app =>
+                {
+                    app.Run(context =>
+                    {
+                        return Task.FromResult(0);
+                    });
+                });
+            var server = new TestServer(builder);
+
+            // The HttpContext will be created and the logger will make sure that the HttpRequest exists and contains reasonable values
+            var result = await server.CreateClient().GetStringAsync("/");
+        }
+
+        private class VerifierLogger : ILogger<IWebHost>
+        {
+            public IDisposable BeginScope<TState>(TState state) => new NoopDispoasble();
+
+            public bool IsEnabled(LogLevel logLevel) => true;
+
+            // This call verifies that fields of HttpRequest are accessed and valid
+            public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) => formatter(state, exception);
+
+            class NoopDispoasble : IDisposable
+            {
+                public void Dispose()
+                {
+                }
+            }
+        }
+    }
+}
diff --git a/src/Hosting/TestHost/test/HttpContextBuilderTests.cs b/src/Hosting/TestHost/test/HttpContextBuilderTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..21539c8988e214bac11966bfd647db9a34c73f26
--- /dev/null
+++ b/src/Hosting/TestHost/test/HttpContextBuilderTests.cs
@@ -0,0 +1,332 @@
+// 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;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Xunit;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+    public class HttpContextBuilderTests
+    {
+        [Fact]
+        public async Task ExpectedValuesAreAvailable()
+        {
+            var builder = new WebHostBuilder().Configure(app => { });
+            var server = new TestServer(builder);
+            server.BaseAddress = new Uri("https://example.com/A/Path/");
+            var context = await server.SendAsync(c =>
+            {
+                c.Request.Method = HttpMethods.Post;
+                c.Request.Path = "/and/file.txt";
+                c.Request.QueryString = new QueryString("?and=query");
+            });
+
+            Assert.True(context.RequestAborted.CanBeCanceled);
+            Assert.Equal("HTTP/1.1", context.Request.Protocol);
+            Assert.Equal("POST", context.Request.Method);
+            Assert.Equal("https", context.Request.Scheme);
+            Assert.Equal("example.com", context.Request.Host.Value);
+            Assert.Equal("/A/Path", context.Request.PathBase.Value);
+            Assert.Equal("/and/file.txt", context.Request.Path.Value);
+            Assert.Equal("?and=query", context.Request.QueryString.Value);
+            Assert.NotNull(context.Request.Body);
+            Assert.NotNull(context.Request.Headers);
+            Assert.NotNull(context.Response.Headers);
+            Assert.NotNull(context.Response.Body);
+            Assert.Equal(404, context.Response.StatusCode);
+            Assert.Null(context.Features.Get<IHttpResponseFeature>().ReasonPhrase);
+        }
+
+        [Fact]
+        public async Task SingleSlashNotMovedToPathBase()
+        {
+            var builder = new WebHostBuilder().Configure(app => { });
+            var server = new TestServer(builder);
+            var context = await server.SendAsync(c =>
+            {
+                c.Request.Path = "/";
+            });
+
+            Assert.Equal("", context.Request.PathBase.Value);
+            Assert.Equal("/", context.Request.Path.Value);
+        }
+
+        [Fact]
+        public async Task MiddlewareOnlySetsHeaders()
+        {
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(c =>
+                {
+                    c.Response.Headers["TestHeader"] = "TestValue";
+                    return Task.FromResult(0);
+                });
+            });
+            var server = new TestServer(builder);
+            var context = await server.SendAsync(c => { });
+
+            Assert.Equal("TestValue", context.Response.Headers["TestHeader"]);
+        }
+
+        [Fact]
+        public async Task BlockingMiddlewareShouldNotBlockClient()
+        {
+            var block = new ManualResetEvent(false);
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(c =>
+                {
+                    block.WaitOne();
+                    return Task.FromResult(0);
+                });
+            });
+            var server = new TestServer(builder);
+            var task = server.SendAsync(c => { });
+
+            Assert.False(task.IsCompleted);
+            Assert.False(task.Wait(50));
+            block.Set();
+            var context = await task;
+        }
+
+        [Fact]
+        public async Task HeadersAvailableBeforeSyncBodyFinished()
+        {
+            var block = new ManualResetEvent(false);
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(c =>
+                {
+                    c.Response.Headers["TestHeader"] = "TestValue";
+                    var bytes = Encoding.UTF8.GetBytes("BodyStarted" + Environment.NewLine);
+                    c.Response.Body.Write(bytes, 0, bytes.Length);
+                    Assert.True(block.WaitOne(TimeSpan.FromSeconds(5)));
+                    bytes = Encoding.UTF8.GetBytes("BodyFinished");
+                    c.Response.Body.Write(bytes, 0, bytes.Length);
+                    return Task.CompletedTask;
+                });
+            });
+            var server = new TestServer(builder);
+            var context = await server.SendAsync(c => { });
+
+            Assert.Equal("TestValue", context.Response.Headers["TestHeader"]);
+            var reader = new StreamReader(context.Response.Body);
+            Assert.Equal("BodyStarted", reader.ReadLine());
+            block.Set();
+            Assert.Equal("BodyFinished", reader.ReadToEnd());
+        }
+
+        [Fact]
+        public async Task HeadersAvailableBeforeAsyncBodyFinished()
+        {
+            var block = new ManualResetEvent(false);
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(async c =>
+                {
+                    c.Response.Headers["TestHeader"] = "TestValue";
+                    await c.Response.WriteAsync("BodyStarted" + Environment.NewLine);
+                    Assert.True(block.WaitOne(TimeSpan.FromSeconds(5)));
+                    await c.Response.WriteAsync("BodyFinished");
+                });
+            });
+            var server = new TestServer(builder);
+            var context = await server.SendAsync(c => { });
+
+            Assert.Equal("TestValue", context.Response.Headers["TestHeader"]);
+            var reader = new StreamReader(context.Response.Body);
+            Assert.Equal("BodyStarted", await reader.ReadLineAsync());
+            block.Set();
+            Assert.Equal("BodyFinished", await reader.ReadToEndAsync());
+        }
+
+        [Fact]
+        public async Task FlushSendsHeaders()
+        {
+            var block = new ManualResetEvent(false);
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(async c =>
+                {
+                    c.Response.Headers["TestHeader"] = "TestValue";
+                    c.Response.Body.Flush();
+                    block.WaitOne();
+                    await c.Response.WriteAsync("BodyFinished");
+                });
+            });
+            var server = new TestServer(builder);
+            var context = await server.SendAsync(c => { });
+
+            Assert.Equal("TestValue", context.Response.Headers["TestHeader"]);
+            block.Set();
+            Assert.Equal("BodyFinished", new StreamReader(context.Response.Body).ReadToEnd());
+        }
+
+        [Fact]
+        public async Task ClientDisposalCloses()
+        {
+            var block = new ManualResetEvent(false);
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(async c =>
+                {
+                    c.Response.Headers["TestHeader"] = "TestValue";
+                    c.Response.Body.Flush();
+                    block.WaitOne();
+                    await c.Response.WriteAsync("BodyFinished");
+                });
+            });
+            var server = new TestServer(builder);
+            var context = await server.SendAsync(c => { });
+
+            Assert.Equal("TestValue", context.Response.Headers["TestHeader"]);
+            var responseStream = context.Response.Body;
+            Task<int> readTask = responseStream.ReadAsync(new byte[100], 0, 100);
+            Assert.False(readTask.IsCompleted);
+            responseStream.Dispose();
+            Assert.True(readTask.Wait(TimeSpan.FromSeconds(10)));
+            Assert.Equal(0, readTask.Result);
+            block.Set();
+        }
+
+        [Fact]
+        public void ClientCancellationAborts()
+        {
+            var block = new ManualResetEvent(false);
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(c =>
+                {
+                    block.Set();
+                    Assert.True(c.RequestAborted.WaitHandle.WaitOne(TimeSpan.FromSeconds(10)));
+                    c.RequestAborted.ThrowIfCancellationRequested();
+                    return Task.CompletedTask;
+                });
+            });
+            var server = new TestServer(builder);
+            var cts = new CancellationTokenSource();
+            var contextTask = server.SendAsync(c => { }, cts.Token);
+            block.WaitOne();
+            cts.Cancel();
+
+            var ex = Assert.Throws<AggregateException>(() => contextTask.Wait(TimeSpan.FromSeconds(10)));
+            Assert.IsAssignableFrom<OperationCanceledException>(ex.GetBaseException());
+        }
+
+        [Fact]
+        public async Task ClientCancellationAbortsReadAsync()
+        {
+            var block = new ManualResetEvent(false);
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(async c =>
+                {
+                    c.Response.Headers["TestHeader"] = "TestValue";
+                    c.Response.Body.Flush();
+                    block.WaitOne();
+                    await c.Response.WriteAsync("BodyFinished");
+                });
+            });
+            var server = new TestServer(builder);
+            var context = await server.SendAsync(c => { });
+
+            Assert.Equal("TestValue", context.Response.Headers["TestHeader"]);
+            var responseStream = context.Response.Body;
+            var cts = new CancellationTokenSource();
+            var readTask = responseStream.ReadAsync(new byte[100], 0, 100, cts.Token);
+            Assert.False(readTask.IsCompleted);
+            cts.Cancel();
+            var ex = Assert.Throws<AggregateException>(() => readTask.Wait(TimeSpan.FromSeconds(10)));
+            Assert.IsAssignableFrom<OperationCanceledException>(ex.GetBaseException());
+            block.Set();
+        }
+
+        [Fact]
+        public Task ExceptionBeforeFirstWriteIsReported()
+        {
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(c =>
+                {
+                    throw new InvalidOperationException("Test Exception");
+                });
+            });
+            var server = new TestServer(builder);
+            return Assert.ThrowsAsync<InvalidOperationException>(() => server.SendAsync(c => { }));
+        }
+
+        [Fact]
+        public async Task ExceptionAfterFirstWriteIsReported()
+        {
+            var block = new ManualResetEvent(false);
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(async c =>
+                {
+                    c.Response.Headers["TestHeader"] = "TestValue";
+                    await c.Response.WriteAsync("BodyStarted");
+                    block.WaitOne();
+                    throw new InvalidOperationException("Test Exception");
+                });
+            });
+            var server = new TestServer(builder);
+            var context = await server.SendAsync(c => { });
+
+            Assert.Equal("TestValue", context.Response.Headers["TestHeader"]);
+            Assert.Equal(11, context.Response.Body.Read(new byte[100], 0, 100));
+            block.Set();
+            var ex = Assert.Throws<IOException>(() => context.Response.Body.Read(new byte[100], 0, 100));
+            Assert.IsAssignableFrom<InvalidOperationException>(ex.InnerException);
+        }
+
+        [Fact]
+        public async Task ClientHandlerCreateContextWithDefaultRequestParameters()
+        {
+            // This logger will attempt to access information from HttpRequest once the HttpContext is created
+            var logger = new VerifierLogger();
+            var builder = new WebHostBuilder()
+                .ConfigureServices(services =>
+                {
+                    services.AddSingleton<ILogger<IWebHost>>(logger);
+                })
+                .Configure(app =>
+                {
+                    app.Run(context =>
+                    {
+                        return Task.FromResult(0);
+                    });
+                });
+            var server = new TestServer(builder);
+
+            // The HttpContext will be created and the logger will make sure that the HttpRequest exists and contains reasonable values
+            var ctx = await server.SendAsync(c => { });
+        }
+
+        private class VerifierLogger : ILogger<IWebHost>
+        {
+            public IDisposable BeginScope<TState>(TState state) => new NoopDispoasble();
+
+            public bool IsEnabled(LogLevel logLevel) => true;
+
+            // This call verifies that fields of HttpRequest are accessed and valid
+            public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) => formatter(state, exception);
+
+            class NoopDispoasble : IDisposable
+            {
+                public void Dispose()
+                {
+                }
+            }
+        }
+    }
+}
diff --git a/src/Hosting/TestHost/test/Microsoft.AspNetCore.TestHost.Tests.csproj b/src/Hosting/TestHost/test/Microsoft.AspNetCore.TestHost.Tests.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..51041799c4824279ffe37758e8261975aefc406b
--- /dev/null
+++ b/src/Hosting/TestHost/test/Microsoft.AspNetCore.TestHost.Tests.csproj
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.TestHost" />
+    <Reference Include="Microsoft.Extensions.DiagnosticAdapter" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Hosting/TestHost/test/RequestBuilderTests.cs b/src/Hosting/TestHost/test/RequestBuilderTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2a37015aae1aea25f4722f9d7c38ac09b8c65898
--- /dev/null
+++ b/src/Hosting/TestHost/test/RequestBuilderTests.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 Microsoft.AspNetCore.Hosting;
+using Xunit;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+    public class RequestBuilderTests
+    {
+        [Fact]
+        public void AddRequestHeader()
+        {
+            var builder = new WebHostBuilder().Configure(app => { });
+            var server = new TestServer(builder);
+            server.CreateRequest("/")
+                .AddHeader("Host", "MyHost:90")
+                .And(request =>
+                {
+                    Assert.Equal("MyHost:90", request.Headers.Host.ToString());
+                });
+        }
+
+        [Fact]
+        public void AddContentHeaders()
+        {
+            var builder = new WebHostBuilder().Configure(app => { });
+            var server = new TestServer(builder);
+            server.CreateRequest("/")
+                .AddHeader("Content-Type", "Test/Value")
+                .And(request =>
+                {
+                    Assert.NotNull(request.Content);
+                    Assert.Equal("Test/Value", request.Content.Headers.ContentType.ToString());
+                });
+        }
+    }
+}
diff --git a/src/Hosting/TestHost/test/ResponseFeatureTests.cs b/src/Hosting/TestHost/test/ResponseFeatureTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f8af4cf64d2469ee0133dee048aa13b5759a5d1c
--- /dev/null
+++ b/src/Hosting/TestHost/test/ResponseFeatureTests.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 System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+    public class ResponseFeatureTests
+    {
+        [Fact]
+        public async Task StatusCode_DefaultsTo200()
+        {
+            // Arrange & Act
+            var responseInformation = new ResponseFeature();
+
+            // Assert
+            Assert.Equal(200, responseInformation.StatusCode);
+            Assert.False(responseInformation.HasStarted);
+
+            await responseInformation.FireOnSendingHeadersAsync();
+
+            Assert.True(responseInformation.HasStarted);
+            Assert.True(responseInformation.Headers.IsReadOnly);
+        }
+
+        [Fact]
+        public void OnStarting_ThrowsWhenHasStarted()
+        {
+            // Arrange
+            var responseInformation = new ResponseFeature();
+            responseInformation.HasStarted = true;
+
+            // Act & Assert
+            Assert.Throws<InvalidOperationException>(() =>
+            {
+                responseInformation.OnStarting((status) =>
+                {
+                    return Task.FromResult(string.Empty);
+                }, state: "string");
+            });
+        }
+
+        [Fact]
+        public void StatusCode_ThrowsWhenHasStarted()
+        {
+            var responseInformation = new ResponseFeature();
+            responseInformation.HasStarted = true;
+
+            Assert.Throws<InvalidOperationException>(() => responseInformation.StatusCode = 400);
+            Assert.Throws<InvalidOperationException>(() => responseInformation.ReasonPhrase = "Hello World");
+        }
+
+        [Fact]
+        public void StatusCode_MustBeGreaterThan99()
+        {
+            var responseInformation = new ResponseFeature();
+
+            Assert.Throws<ArgumentOutOfRangeException>(() => responseInformation.StatusCode = 99);
+            Assert.Throws<ArgumentOutOfRangeException>(() => responseInformation.StatusCode = 0);
+            Assert.Throws<ArgumentOutOfRangeException>(() => responseInformation.StatusCode = -200);
+            responseInformation.StatusCode = 100;
+            responseInformation.StatusCode = 200;
+            responseInformation.StatusCode = 1000;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/TestHost/test/TestClientTests.cs b/src/Hosting/TestHost/test/TestClientTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3101c2965f2d7207ef027e745e9176f8c6b76cd5
--- /dev/null
+++ b/src/Hosting/TestHost/test/TestClientTests.cs
@@ -0,0 +1,429 @@
+// 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.Net.Http;
+using System.Net.WebSockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Testing.xunit;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Xunit;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+    public class TestClientTests
+    {
+        [Fact]
+        public async Task GetAsyncWorks()
+        {
+            // Arrange
+            var expected = "GET Response";
+            RequestDelegate appDelegate = ctx =>
+                ctx.Response.WriteAsync(expected);
+            var builder = new WebHostBuilder().Configure(app => app.Run(appDelegate));
+            var server = new TestServer(builder);
+            var client = server.CreateClient();
+
+            // Act
+            var actual = await client.GetStringAsync("http://localhost:12345");
+
+            // Assert
+            Assert.Equal(expected, actual);
+        }
+
+        [Fact]
+        public async Task NoTrailingSlash_NoPathBase()
+        {
+            // Arrange
+            var expected = "GET Response";
+            RequestDelegate appDelegate = ctx =>
+            {
+                Assert.Equal("", ctx.Request.PathBase.Value);
+                Assert.Equal("/", ctx.Request.Path.Value);
+                return ctx.Response.WriteAsync(expected);
+            };
+            var builder = new WebHostBuilder().Configure(app => app.Run(appDelegate));
+            var server = new TestServer(builder);
+            var client = server.CreateClient();
+
+            // Act
+            var actual = await client.GetStringAsync("http://localhost:12345");
+
+            // Assert
+            Assert.Equal(expected, actual);
+        }
+
+        [Fact]
+        public async Task SingleTrailingSlash_NoPathBase()
+        {
+            // Arrange
+            var expected = "GET Response";
+            RequestDelegate appDelegate = ctx =>
+            {
+                Assert.Equal("", ctx.Request.PathBase.Value);
+                Assert.Equal("/", ctx.Request.Path.Value);
+                return ctx.Response.WriteAsync(expected);
+            };
+            var builder = new WebHostBuilder().Configure(app => app.Run(appDelegate));
+            var server = new TestServer(builder);
+            var client = server.CreateClient();
+
+            // Act
+            var actual = await client.GetStringAsync("http://localhost:12345/");
+
+            // Assert
+            Assert.Equal(expected, actual);
+        }
+
+        [Fact]
+        public async Task PutAsyncWorks()
+        {
+            // Arrange
+            RequestDelegate appDelegate = ctx =>
+                ctx.Response.WriteAsync(new StreamReader(ctx.Request.Body).ReadToEnd() + " PUT Response");
+            var builder = new WebHostBuilder().Configure(app => app.Run(appDelegate));
+            var server = new TestServer(builder);
+            var client = server.CreateClient();
+
+            // Act
+            var content = new StringContent("Hello world");
+            var response = await client.PutAsync("http://localhost:12345", content);
+
+            // Assert
+            Assert.Equal("Hello world PUT Response", await response.Content.ReadAsStringAsync());
+        }
+
+        [Fact]
+        public async Task PostAsyncWorks()
+        {
+            // Arrange
+            RequestDelegate appDelegate = async ctx =>
+                await ctx.Response.WriteAsync(new StreamReader(ctx.Request.Body).ReadToEnd() + " POST Response");
+            var builder = new WebHostBuilder().Configure(app => app.Run(appDelegate));
+            var server = new TestServer(builder);
+            var client = server.CreateClient();
+
+            // Act
+            var content = new StringContent("Hello world");
+            var response = await client.PostAsync("http://localhost:12345", content);
+
+            // Assert
+            Assert.Equal("Hello world POST Response", await response.Content.ReadAsStringAsync());
+        }
+
+        [Fact]
+        public async Task LargePayload_DisposesRequest_AfterResponseIsCompleted()
+        {
+            // Arrange
+            var data = new byte[2048];
+            var character = Encoding.ASCII.GetBytes("a");
+
+            for (var i = 0; i < data.Length; i++)
+            {
+                data[i] = character[0];
+            }
+
+            var builder = new WebHostBuilder();
+            RequestDelegate app = (ctx) =>
+            {
+                var disposable = new TestDisposable();
+                ctx.Response.RegisterForDispose(disposable);
+                ctx.Response.Body.Write(data, 0, 1024);
+
+                Assert.False(disposable.IsDisposed);
+
+                ctx.Response.Body.Write(data, 1024, 1024);
+                return Task.FromResult(0);
+            };
+
+            builder.Configure(appBuilder => appBuilder.Run(app));
+            var server = new TestServer(builder);
+            var client = server.CreateClient();
+
+            // Act & Assert
+            var response = await client.GetAsync("http://localhost:12345");
+        }
+
+        private class TestDisposable : IDisposable
+        {
+            public bool IsDisposed { get; private set; }
+
+            public void Dispose()
+            {
+                IsDisposed = true;
+            }
+        }
+
+        [Fact]
+        public async Task WebSocketWorks()
+        {
+            // Arrange
+            // This logger will attempt to access information from HttpRequest once the HttpContext is created
+            var logger = new VerifierLogger();
+            RequestDelegate appDelegate = async ctx =>
+            {
+                if (ctx.WebSockets.IsWebSocketRequest)
+                {
+                    var websocket = await ctx.WebSockets.AcceptWebSocketAsync();
+                    var receiveArray = new byte[1024];
+                    while (true)
+                    {
+                        var receiveResult = await websocket.ReceiveAsync(new System.ArraySegment<byte>(receiveArray), CancellationToken.None);
+                        if (receiveResult.MessageType == WebSocketMessageType.Close)
+                        {
+                            await websocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Normal Closure", CancellationToken.None);
+                            break;
+                        }
+                        else
+                        {
+                            var sendBuffer = new System.ArraySegment<byte>(receiveArray, 0, receiveResult.Count);
+                            await websocket.SendAsync(sendBuffer, receiveResult.MessageType, receiveResult.EndOfMessage, CancellationToken.None);
+                        }
+                    }
+                }
+            };
+            var builder = new WebHostBuilder()
+                .ConfigureServices(services =>
+                {
+                    services.AddSingleton<ILogger<IWebHost>>(logger);
+                })
+                .Configure(app =>
+                {
+                    app.Run(appDelegate);
+                });
+            var server = new TestServer(builder);
+
+            // Act
+            var client = server.CreateWebSocketClient();
+            // The HttpContext will be created and the logger will make sure that the HttpRequest exists and contains reasonable values
+            var clientSocket = await client.ConnectAsync(new System.Uri("http://localhost"), CancellationToken.None);
+            var hello = Encoding.UTF8.GetBytes("hello");
+            await clientSocket.SendAsync(new System.ArraySegment<byte>(hello), WebSocketMessageType.Text, true, CancellationToken.None);
+            var world = Encoding.UTF8.GetBytes("world!");
+            await clientSocket.SendAsync(new System.ArraySegment<byte>(world), WebSocketMessageType.Binary, true, CancellationToken.None);
+            await clientSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Normal Closure", CancellationToken.None);
+
+            // Assert
+            Assert.Equal(WebSocketState.CloseSent, clientSocket.State);
+
+            var buffer = new byte[1024];
+            var result = await clientSocket.ReceiveAsync(new System.ArraySegment<byte>(buffer), CancellationToken.None);
+            Assert.Equal(hello.Length, result.Count);
+            Assert.True(hello.SequenceEqual(buffer.Take(hello.Length)));
+            Assert.Equal(WebSocketMessageType.Text, result.MessageType);
+
+            result = await clientSocket.ReceiveAsync(new System.ArraySegment<byte>(buffer), CancellationToken.None);
+            Assert.Equal(world.Length, result.Count);
+            Assert.True(world.SequenceEqual(buffer.Take(world.Length)));
+            Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
+
+            result = await clientSocket.ReceiveAsync(new System.ArraySegment<byte>(buffer), CancellationToken.None);
+            Assert.Equal(WebSocketMessageType.Close, result.MessageType);
+            Assert.Equal(WebSocketState.Closed, clientSocket.State);
+
+            clientSocket.Dispose();
+        }
+
+        [ConditionalFact]
+        public async Task WebSocketAcceptThrowsWhenCancelled()
+        {
+            // Arrange
+            // This logger will attempt to access information from HttpRequest once the HttpContext is created
+            var logger = new VerifierLogger();
+            RequestDelegate appDelegate = async ctx =>
+            {
+                if (ctx.WebSockets.IsWebSocketRequest)
+                {
+                    var websocket = await ctx.WebSockets.AcceptWebSocketAsync();
+                    var receiveArray = new byte[1024];
+                    while (true)
+                    {
+                        var receiveResult = await websocket.ReceiveAsync(new ArraySegment<byte>(receiveArray), CancellationToken.None);
+                        if (receiveResult.MessageType == WebSocketMessageType.Close)
+                        {
+                            await websocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Normal Closure", CancellationToken.None);
+                            break;
+                        }
+                        else
+                        {
+                            var sendBuffer = new System.ArraySegment<byte>(receiveArray, 0, receiveResult.Count);
+                            await websocket.SendAsync(sendBuffer, receiveResult.MessageType, receiveResult.EndOfMessage, CancellationToken.None);
+                        }
+                    }
+                }
+            };
+            var builder = new WebHostBuilder()
+                .ConfigureServices(services => services.AddSingleton<ILogger<IWebHost>>(logger))
+                .Configure(app => app.Run(appDelegate));
+            var server = new TestServer(builder);
+
+            // Act
+            var client = server.CreateWebSocketClient();
+            var tokenSource = new CancellationTokenSource();
+            tokenSource.Cancel();
+
+            // Assert
+            await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await client.ConnectAsync(new Uri("http://localhost"), tokenSource.Token));
+        }
+
+        private class VerifierLogger : ILogger<IWebHost>
+        {
+            public IDisposable BeginScope<TState>(TState state) => new NoopDispoasble();
+
+            public bool IsEnabled(LogLevel logLevel) => true;
+
+            // This call verifies that fields of HttpRequest are accessed and valid
+            public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) => formatter(state, exception);
+
+            class NoopDispoasble : IDisposable
+            {
+                public void Dispose()
+                {
+                }
+            }
+        }
+
+        [Fact]
+        public async Task WebSocketDisposalThrowsOnPeer()
+        {
+            // Arrange
+            RequestDelegate appDelegate = async ctx =>
+            {
+                if (ctx.WebSockets.IsWebSocketRequest)
+                {
+                    var websocket = await ctx.WebSockets.AcceptWebSocketAsync();
+                    websocket.Dispose();
+                }
+            };
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(appDelegate);
+            });
+            var server = new TestServer(builder);
+
+            // Act
+            var client = server.CreateWebSocketClient();
+            var clientSocket = await client.ConnectAsync(new System.Uri("http://localhost"), CancellationToken.None);
+            var buffer = new byte[1024];
+            await Assert.ThrowsAsync<IOException>(async () => await clientSocket.ReceiveAsync(new System.ArraySegment<byte>(buffer), CancellationToken.None));
+
+            clientSocket.Dispose();
+        }
+
+        [Fact]
+        public async Task WebSocketTinyReceiveGeneratesEndOfMessage()
+        {
+            // Arrange
+            RequestDelegate appDelegate = async ctx =>
+            {
+                if (ctx.WebSockets.IsWebSocketRequest)
+                {
+                    var websocket = await ctx.WebSockets.AcceptWebSocketAsync();
+                    var receiveArray = new byte[1024];
+                    while (true)
+                    {
+                        var receiveResult = await websocket.ReceiveAsync(new System.ArraySegment<byte>(receiveArray), CancellationToken.None);
+                        var sendBuffer = new System.ArraySegment<byte>(receiveArray, 0, receiveResult.Count);
+                        await websocket.SendAsync(sendBuffer, receiveResult.MessageType, receiveResult.EndOfMessage, CancellationToken.None);
+                    }
+                }
+            };
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(appDelegate);
+            });
+            var server = new TestServer(builder);
+
+            // Act
+            var client = server.CreateWebSocketClient();
+            var clientSocket = await client.ConnectAsync(new System.Uri("http://localhost"), CancellationToken.None);
+            var hello = Encoding.UTF8.GetBytes("hello");
+            await clientSocket.SendAsync(new System.ArraySegment<byte>(hello), WebSocketMessageType.Text, true, CancellationToken.None);
+
+            // Assert
+            var buffer = new byte[1];
+            for (var i = 0; i < hello.Length; i++)
+            {
+                bool last = i == (hello.Length - 1);
+                var result = await clientSocket.ReceiveAsync(new System.ArraySegment<byte>(buffer), CancellationToken.None);
+                Assert.Equal(buffer.Length, result.Count);
+                Assert.Equal(buffer[0], hello[i]);
+                Assert.Equal(last, result.EndOfMessage);
+            }
+
+            clientSocket.Dispose();
+        }
+
+        [Fact]
+        public async Task ClientDisposalAbortsRequest()
+        {
+            // Arrange
+            TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
+            RequestDelegate appDelegate = async ctx =>
+            {
+                // Write Headers
+                await ctx.Response.Body.FlushAsync();
+
+                var sem = new SemaphoreSlim(0);
+                try
+                {
+                    await sem.WaitAsync(ctx.RequestAborted);
+                }
+                catch (Exception e)
+                {
+                    tcs.SetException(e);
+                }
+            };
+
+            // Act
+            var builder = new WebHostBuilder().Configure(app => app.Run(appDelegate));
+            var server = new TestServer(builder);
+            var client = server.CreateClient();
+            var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:12345");
+            var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
+            // Abort Request
+            response.Dispose();
+
+            // Assert
+            var exception = await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await tcs.Task);
+        }
+
+        [Fact]
+        public async Task ClientCancellationAbortsRequest()
+        {
+            // Arrange
+            TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
+            RequestDelegate appDelegate = async ctx =>
+            {
+                var sem = new SemaphoreSlim(0);
+                try
+                {
+                    await sem.WaitAsync(ctx.RequestAborted);
+                }
+                catch (Exception e)
+                {
+                    tcs.SetException(e);
+                }
+            };
+
+            // Act
+            var builder = new WebHostBuilder().Configure(app => app.Run(appDelegate));
+            var server = new TestServer(builder);
+            var client = server.CreateClient();
+            var cts = new CancellationTokenSource();
+            cts.CancelAfter(500);
+            var response = await client.GetAsync("http://localhost:12345", cts.Token);
+
+            // Assert
+            var exception = await Assert.ThrowsAnyAsync<OperationCanceledException>(async () => await tcs.Task);
+        }
+    }
+}
diff --git a/src/Hosting/TestHost/test/TestServerTests.cs b/src/Hosting/TestHost/test/TestServerTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b1eee1fd9495973cd57df121dbf7e890cdf7cc01
--- /dev/null
+++ b/src/Hosting/TestHost/test/TestServerTests.cs
@@ -0,0 +1,687 @@
+// 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.Net;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Testing.xunit;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DiagnosticAdapter;
+using Microsoft.Extensions.Logging;
+using Xunit;
+
+namespace Microsoft.AspNetCore.TestHost
+{
+    public class TestServerTests
+    {
+        [Fact]
+        public void CreateWithDelegate()
+        {
+            // Arrange
+            // Act & Assert (Does not throw)
+            new TestServer(new WebHostBuilder().Configure(app => { }));
+        }
+
+        [Fact]
+        public void DoesNotCaptureStartupErrorsByDefault()
+        {
+            var builder = new WebHostBuilder()
+                .Configure(app =>
+                {
+                    throw new InvalidOperationException();
+                });
+
+            Assert.Throws<InvalidOperationException>(() => new TestServer(builder));
+        }
+
+        [Fact]
+        public async Task ServicesCanBeOverridenForTestingAsync()
+        {
+            var builder = new WebHostBuilder()
+                .ConfigureServices(s => s.AddSingleton<IServiceProviderFactory<ThirdPartyContainer>, ThirdPartyContainerServiceProviderFactory>())
+                .UseStartup<ThirdPartyContainerStartup>()
+                .ConfigureTestServices(services => services.AddSingleton(new SimpleService { Message = "OverridesConfigureServices" }))
+                .ConfigureTestContainer<ThirdPartyContainer>(container => container.Services.AddSingleton(new TestService { Message = "OverridesConfigureContainer" }));
+
+            var host = new TestServer(builder);
+
+            var response = await host.CreateClient().GetStringAsync("/");
+
+            Assert.Equal("OverridesConfigureServices, OverridesConfigureContainer", response);
+        }
+
+        public class ThirdPartyContainerStartup
+        {
+            public void ConfigureServices(IServiceCollection services) =>
+                services.AddSingleton(new SimpleService { Message = "ConfigureServices" });
+
+            public void ConfigureContainer(ThirdPartyContainer container) =>
+                container.Services.AddSingleton(new TestService { Message = "ConfigureContainer" });
+
+            public void Configure(IApplicationBuilder app) =>
+                app.Use((ctx, next) => ctx.Response.WriteAsync(
+                    $"{ctx.RequestServices.GetRequiredService<SimpleService>().Message}, {ctx.RequestServices.GetRequiredService<TestService>().Message}"));
+        }
+
+        public class ThirdPartyContainer
+        {
+            public IServiceCollection Services { get; set; }
+        }
+
+        public class ThirdPartyContainerServiceProviderFactory : IServiceProviderFactory<ThirdPartyContainer>
+        {
+            public ThirdPartyContainer CreateBuilder(IServiceCollection services) => new ThirdPartyContainer { Services = services };
+
+            public IServiceProvider CreateServiceProvider(ThirdPartyContainer containerBuilder) => containerBuilder.Services.BuildServiceProvider();
+        }
+
+        [Fact]
+        public void CaptureStartupErrorsSettingPreserved()
+        {
+            var builder = new WebHostBuilder()
+                .CaptureStartupErrors(true)
+                .Configure(app =>
+                {
+                    throw new InvalidOperationException();
+                });
+
+            // Does not throw
+            new TestServer(builder);
+        }
+
+        [Fact]
+        public void ApplicationServicesAvailableFromTestServer()
+        {
+            var testService = new TestService();
+            var builder = new WebHostBuilder()
+                .Configure(app => { })
+                .ConfigureServices(services =>
+                {
+                    services.AddSingleton(testService);
+                });
+            var server = new TestServer(builder);
+
+            Assert.Equal(testService, server.Host.Services.GetRequiredService<TestService>());
+        }
+
+        [Fact]
+        public async Task RequestServicesAutoCreated()
+        {
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(context =>
+                {
+                    return context.Response.WriteAsync("RequestServices:" + (context.RequestServices != null));
+                });
+            });
+            var server = new TestServer(builder);
+
+            string result = await server.CreateClient().GetStringAsync("/path");
+            Assert.Equal("RequestServices:True", result);
+        }
+
+        public class CustomContainerStartup
+        {
+            public IServiceProvider Services;
+            public IServiceProvider ConfigureServices(IServiceCollection services)
+            {
+                Services = services.BuildServiceProvider();
+                return Services;
+            }
+
+            public void Configure(IApplicationBuilder app)
+            {
+                var applicationServices = app.ApplicationServices;
+                app.Run(async context =>
+                {
+                    await context.Response.WriteAsync("ApplicationServicesEqual:" + (applicationServices == Services));
+                });
+            }
+
+        }
+
+        [Fact]
+        public async Task CustomServiceProviderSetsApplicationServices()
+        {
+            var builder = new WebHostBuilder().UseStartup<CustomContainerStartup>();
+            var server = new TestServer(builder);
+            string result = await server.CreateClient().GetStringAsync("/path");
+            Assert.Equal("ApplicationServicesEqual:True", result);
+        }
+
+        [Fact]
+        public void TestServerConstructorWithFeatureCollectionAllowsInitializingServerFeatures()
+        {
+            // Arrange
+            var url = "http://localhost:8000/appName/serviceName";
+            var builder = new WebHostBuilder()
+                .UseUrls(url)
+                .Configure(applicationBuilder =>
+                {
+                    var serverAddressesFeature = applicationBuilder.ServerFeatures.Get<IServerAddressesFeature>();
+                    Assert.Contains(serverAddressesFeature.Addresses, s => string.Equals(s, url, StringComparison.Ordinal));
+                });
+
+
+            var featureCollection = new FeatureCollection();
+            featureCollection.Set<IServerAddressesFeature>(new ServerAddressesFeature());
+
+            // Act
+            new TestServer(builder, featureCollection);
+
+            // Assert
+            // Is inside configure callback
+        }
+
+        [Fact]
+        public void TestServerConstructorWithNullFeatureCollectionThrows()
+        {
+            var builder = new WebHostBuilder()
+                .Configure(b => { });
+
+            Assert.Throws<ArgumentNullException>(() => new TestServer(builder, null));
+        }
+
+        public class TestService { public string Message { get; set; } }
+
+        public class TestRequestServiceMiddleware
+        {
+            private RequestDelegate _next;
+
+            public TestRequestServiceMiddleware(RequestDelegate next)
+            {
+                _next = next;
+            }
+
+            public Task Invoke(HttpContext httpContext)
+            {
+                var services = new ServiceCollection();
+                services.AddTransient<TestService>();
+                httpContext.RequestServices = services.BuildServiceProvider();
+
+                return _next.Invoke(httpContext);
+            }
+        }
+
+        public class RequestServicesFilter : IStartupFilter
+        {
+            public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
+            {
+                return builder =>
+                {
+                    builder.UseMiddleware<TestRequestServiceMiddleware>();
+                    next(builder);
+                };
+            }
+        }
+
+        [Fact]
+        public async Task ExistingRequestServicesWillNotBeReplaced()
+        {
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(context =>
+                {
+                    var service = context.RequestServices.GetService<TestService>();
+                    return context.Response.WriteAsync("Found:" + (service != null));
+                });
+            })
+            .ConfigureServices(services =>
+            {
+                services.AddTransient<IStartupFilter, RequestServicesFilter>();
+            });
+            var server = new TestServer(builder);
+
+            string result = await server.CreateClient().GetStringAsync("/path");
+            Assert.Equal("Found:True", result);
+        }
+
+        [Fact]
+        public async Task CanSetCustomServiceProvider()
+        {
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(context =>
+                {
+                    context.RequestServices = new ServiceCollection()
+                    .AddTransient<TestService>()
+                    .BuildServiceProvider();
+
+                    var s = context.RequestServices.GetRequiredService<TestService>();
+
+                    return context.Response.WriteAsync("Success");
+                });
+            });
+            var server = new TestServer(builder);
+
+            string result = await server.CreateClient().GetStringAsync("/path");
+            Assert.Equal("Success", result);
+        }
+
+        public class ReplaceServiceProvidersFeatureFilter : IStartupFilter, IServiceProvidersFeature
+        {
+            public ReplaceServiceProvidersFeatureFilter(IServiceProvider appServices, IServiceProvider requestServices)
+            {
+                ApplicationServices = appServices;
+                RequestServices = requestServices;
+            }
+
+            public IServiceProvider ApplicationServices { get; set; }
+
+            public IServiceProvider RequestServices { get; set; }
+
+            public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
+            {
+                return app =>
+                {
+                    app.Use(async (context, nxt) =>
+                    {
+                        context.Features.Set<IServiceProvidersFeature>(this);
+                        await nxt();
+                    });
+                    next(app);
+                };
+            }
+        }
+
+        [Fact]
+        public async Task ExistingServiceProviderFeatureWillNotBeReplaced()
+        {
+            var appServices = new ServiceCollection().BuildServiceProvider();
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(context =>
+                {
+                    Assert.Equal(appServices, context.RequestServices);
+                    return context.Response.WriteAsync("Success");
+                });
+            })
+            .ConfigureServices(services =>
+            {
+                services.AddSingleton<IStartupFilter>(new ReplaceServiceProvidersFeatureFilter(appServices, appServices));
+            });
+            var server = new TestServer(builder);
+
+            var result = await server.CreateClient().GetStringAsync("/path");
+            Assert.Equal("Success", result);
+        }
+
+        public class NullServiceProvidersFeatureFilter : IStartupFilter, IServiceProvidersFeature
+        {
+            public IServiceProvider ApplicationServices { get; set; }
+
+            public IServiceProvider RequestServices { get; set; }
+
+            public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
+            {
+                return app =>
+                {
+                    app.Use(async (context, nxt) =>
+                    {
+                        context.Features.Set<IServiceProvidersFeature>(this);
+                        await nxt();
+                    });
+                    next(app);
+                };
+            }
+        }
+
+        [Fact]
+        public async Task WillReplaceServiceProviderFeatureWithNullRequestServices()
+        {
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(context =>
+                {
+                    Assert.Null(context.RequestServices);
+                    return context.Response.WriteAsync("Success");
+                });
+            })
+            .ConfigureServices(services =>
+            {
+                services.AddTransient<IStartupFilter, NullServiceProvidersFeatureFilter>();
+            });
+            var server = new TestServer(builder);
+
+            var result = await server.CreateClient().GetStringAsync("/path");
+            Assert.Equal("Success", result);
+        }
+
+        [Fact]
+        public async Task CanAccessLogger()
+        {
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(context =>
+                {
+                    var logger = app.ApplicationServices.GetRequiredService<ILogger<HttpContext>>();
+                    return context.Response.WriteAsync("FoundLogger:" + (logger != null));
+                });
+            });
+            var server = new TestServer(builder);
+
+            string result = await server.CreateClient().GetStringAsync("/path");
+            Assert.Equal("FoundLogger:True", result);
+        }
+
+        [Fact]
+        public async Task CanAccessHttpContext()
+        {
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(context =>
+                {
+                    var accessor = app.ApplicationServices.GetRequiredService<IHttpContextAccessor>();
+                    return context.Response.WriteAsync("HasContext:" + (accessor.HttpContext != null));
+                });
+            })
+            .ConfigureServices(services =>
+            {
+                services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
+            });
+            var server = new TestServer(builder);
+
+            string result = await server.CreateClient().GetStringAsync("/path");
+            Assert.Equal("HasContext:True", result);
+        }
+
+        public class ContextHolder
+        {
+            public ContextHolder(IHttpContextAccessor accessor)
+            {
+                Accessor = accessor;
+            }
+
+            public IHttpContextAccessor Accessor { get; set; }
+        }
+
+        [Fact]
+        public async Task CanAddNewHostServices()
+        {
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(context =>
+                {
+                    var accessor = app.ApplicationServices.GetRequiredService<ContextHolder>();
+                    return context.Response.WriteAsync("HasContext:" + (accessor.Accessor.HttpContext != null));
+                });
+            })
+            .ConfigureServices(services =>
+            {
+                services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
+                services.AddSingleton<ContextHolder>();
+            });
+            var server = new TestServer(builder);
+
+            string result = await server.CreateClient().GetStringAsync("/path");
+            Assert.Equal("HasContext:True", result);
+        }
+
+        [Fact]
+        public async Task CreateInvokesApp()
+        {
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(context =>
+                {
+                    return context.Response.WriteAsync("CreateInvokesApp");
+                });
+            });
+            var server = new TestServer(builder);
+
+            string result = await server.CreateClient().GetStringAsync("/path");
+            Assert.Equal("CreateInvokesApp", result);
+        }
+
+        [Fact]
+        public async Task DisposeStreamIgnored()
+        {
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(async context =>
+                {
+                    await context.Response.WriteAsync("Response");
+                    context.Response.Body.Dispose();
+                });
+            });
+            var server = new TestServer(builder);
+
+            HttpResponseMessage result = await server.CreateClient().GetAsync("/");
+            Assert.Equal(HttpStatusCode.OK, result.StatusCode);
+            Assert.Equal("Response", await result.Content.ReadAsStringAsync());
+        }
+
+        [Fact]
+        public async Task DisposedServerThrows()
+        {
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                app.Run(async context =>
+                {
+                    await context.Response.WriteAsync("Response");
+                    context.Response.Body.Dispose();
+                });
+            });
+            var server = new TestServer(builder);
+
+            HttpResponseMessage result = await server.CreateClient().GetAsync("/");
+            Assert.Equal(HttpStatusCode.OK, result.StatusCode);
+            server.Dispose();
+            await Assert.ThrowsAsync<ObjectDisposedException>(() => server.CreateClient().GetAsync("/"));
+        }
+
+        [Fact]
+        public async Task CancelAborts()
+        {
+            var builder = new WebHostBuilder()
+                                  .Configure(app =>
+                                  {
+                                      app.Run(context =>
+                                      {
+                                          TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
+                                          tcs.SetCanceled();
+                                          return tcs.Task;
+                                      });
+                                  });
+            var server = new TestServer(builder);
+
+            await Assert.ThrowsAsync<TaskCanceledException>(async () => { string result = await server.CreateClient().GetStringAsync("/path"); });
+        }
+
+        [Fact]
+        public async Task CanCreateViaStartupType()
+        {
+            var builder = new WebHostBuilder()
+                .UseStartup<TestStartup>();
+            var server = new TestServer(builder);
+            HttpResponseMessage result = await server.CreateClient().GetAsync("/");
+            Assert.Equal(HttpStatusCode.OK, result.StatusCode);
+            Assert.Equal("FoundService:True", await result.Content.ReadAsStringAsync());
+        }
+
+        [Fact]
+        public async Task CanCreateViaStartupTypeAndSpecifyEnv()
+        {
+            var builder = new WebHostBuilder()
+                            .UseStartup<TestStartup>()
+                            .UseEnvironment("Foo");
+            var server = new TestServer(builder);
+
+            HttpResponseMessage result = await server.CreateClient().GetAsync("/");
+            Assert.Equal(HttpStatusCode.OK, result.StatusCode);
+            Assert.Equal("FoundFoo:False", await result.Content.ReadAsStringAsync());
+        }
+
+        [Fact]
+        public async Task BeginEndDiagnosticAvailable()
+        {
+            DiagnosticListener diagnosticListener = null;
+
+            var builder = new WebHostBuilder()
+                            .Configure(app =>
+                            {
+                                diagnosticListener = app.ApplicationServices.GetRequiredService<DiagnosticListener>();
+                                app.Run(context =>
+                                {
+                                    return context.Response.WriteAsync("Hello World");
+                                });
+                            });
+            var server = new TestServer(builder);
+
+            var listener = new TestDiagnosticListener();
+            diagnosticListener.SubscribeWithAdapter(listener);
+            var result = await server.CreateClient().GetStringAsync("/path");
+
+            // This ensures that all diagnostics are completely written to the diagnostic listener
+            Thread.Sleep(1000);
+
+            Assert.Equal("Hello World", result);
+            Assert.NotNull(listener.BeginRequest?.HttpContext);
+            Assert.NotNull(listener.EndRequest?.HttpContext);
+            Assert.Null(listener.UnhandledException);
+        }
+
+        [Fact]
+        public async Task ExceptionDiagnosticAvailable()
+        {
+            DiagnosticListener diagnosticListener = null;
+            var builder = new WebHostBuilder().Configure(app =>
+            {
+                diagnosticListener = app.ApplicationServices.GetRequiredService<DiagnosticListener>();
+                app.Run(context =>
+                {
+                    throw new Exception("Test exception");
+                });
+            });
+            var server = new TestServer(builder);
+
+            var listener = new TestDiagnosticListener();
+            diagnosticListener.SubscribeWithAdapter(listener);
+            await Assert.ThrowsAsync<Exception>(() => server.CreateClient().GetAsync("/path"));
+
+            // This ensures that all diagnostics are completely written to the diagnostic listener
+            Thread.Sleep(1000);
+
+            Assert.NotNull(listener.BeginRequest?.HttpContext);
+            Assert.Null(listener.EndRequest?.HttpContext);
+            Assert.NotNull(listener.UnhandledException?.HttpContext);
+            Assert.NotNull(listener.UnhandledException?.Exception);
+        }
+
+        public class TestDiagnosticListener
+        {
+            public class OnBeginRequestEventData
+            {
+                public IProxyHttpContext HttpContext { get; set; }
+            }
+
+            public OnBeginRequestEventData BeginRequest { get; set; }
+
+            [DiagnosticName("Microsoft.AspNetCore.Hosting.BeginRequest")]
+            public virtual void OnBeginRequest(IProxyHttpContext httpContext)
+            {
+                BeginRequest = new OnBeginRequestEventData()
+                {
+                    HttpContext = httpContext,
+                };
+            }
+
+            public class OnEndRequestEventData
+            {
+                public IProxyHttpContext HttpContext { get; set; }
+            }
+
+            public OnEndRequestEventData EndRequest { get; set; }
+
+            [DiagnosticName("Microsoft.AspNetCore.Hosting.EndRequest")]
+            public virtual void OnEndRequest(IProxyHttpContext httpContext)
+            {
+                EndRequest = new OnEndRequestEventData()
+                {
+                    HttpContext = httpContext,
+                };
+            }
+
+            public class OnUnhandledExceptionEventData
+            {
+                public IProxyHttpContext HttpContext { get; set; }
+                public IProxyException Exception { get; set; }
+            }
+
+            public OnUnhandledExceptionEventData UnhandledException { get; set; }
+
+            [DiagnosticName("Microsoft.AspNetCore.Hosting.UnhandledException")]
+            public virtual void OnUnhandledException(IProxyHttpContext httpContext, IProxyException exception)
+            {
+                UnhandledException = new OnUnhandledExceptionEventData()
+                {
+                    HttpContext = httpContext,
+                    Exception = exception,
+                };
+            }
+        }
+
+        public interface IProxyHttpContext
+        {
+        }
+
+        public interface IProxyException
+        {
+        }
+
+        public class Startup
+        {
+            public void Configure(IApplicationBuilder builder)
+            {
+                builder.Run(ctx => ctx.Response.WriteAsync("Startup"));
+            }
+        }
+
+        public class SimpleService
+        {
+            public SimpleService()
+            {
+            }
+
+            public string Message { get; set; }
+        }
+
+        public class TestStartup
+        {
+            public void ConfigureServices(IServiceCollection services)
+            {
+                services.AddSingleton<SimpleService>();
+            }
+
+            public void ConfigureFooServices(IServiceCollection services)
+            {
+            }
+
+            public void Configure(IApplicationBuilder app)
+            {
+                app.Run(context =>
+                {
+                    var service = app.ApplicationServices.GetRequiredService<SimpleService>();
+                    return context.Response.WriteAsync("FoundService:" + (service != null));
+                });
+            }
+
+            public void ConfigureFoo(IApplicationBuilder app)
+            {
+                app.Run(context =>
+                {
+                    var service = app.ApplicationServices.GetService<SimpleService>();
+                    return context.Response.WriteAsync("FoundFoo:" + (service != null));
+                });
+            }
+        }
+    }
+}
diff --git a/src/Hosting/WindowsServices/src/Microsoft.AspNetCore.Hosting.WindowsServices.csproj b/src/Hosting/WindowsServices/src/Microsoft.AspNetCore.Hosting.WindowsServices.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..0c4112a61c28b7ae0ff4599d6e9dbcae85c694ec
--- /dev/null
+++ b/src/Hosting/WindowsServices/src/Microsoft.AspNetCore.Hosting.WindowsServices.csproj
@@ -0,0 +1,21 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>ASP.NET Core hosting infrastructure and startup logic for web applications running within a Windows service.</Description>
+    <TargetFrameworks>net461;netstandard2.0</TargetFrameworks>
+    <NoWarn>$(NoWarn);CS1591</NoWarn>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore;hosting</PackageTags>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Hosting" />
+  </ItemGroup>
+  <ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
+    <Reference Include="System.ServiceProcess" />
+  </ItemGroup>
+  <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
+    <Reference Include="System.ServiceProcess.ServiceController" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Hosting/WindowsServices/src/WebHostService.cs b/src/Hosting/WindowsServices/src/WebHostService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f468d05fe3eb36067b1f1b2d2519c8a0522f20ce
--- /dev/null
+++ b/src/Hosting/WindowsServices/src/WebHostService.cs
@@ -0,0 +1,84 @@
+// 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.ServiceProcess;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Hosting.WindowsServices
+{
+    /// <summary>
+    ///     Provides an implementation of a Windows service that hosts ASP.NET Core.
+    /// </summary>
+    public class WebHostService : ServiceBase
+    {
+        private IWebHost _host;
+        private bool _stopRequestedByWindows;
+
+        /// <summary>
+        /// Creates an instance of <c>WebHostService</c> which hosts the specified web application.
+        /// </summary>
+        /// <param name="host">The configured web host containing the web application to host in the Windows service.</param>
+        public WebHostService(IWebHost host)
+        {
+            _host = host ?? throw new ArgumentNullException(nameof(host));
+        }
+
+        protected sealed override void OnStart(string[] args)
+        {
+            OnStarting(args);
+
+            _host
+                .Services
+                .GetRequiredService<IApplicationLifetime>()
+                .ApplicationStopped
+                .Register(() =>
+                {
+                    if (!_stopRequestedByWindows)
+                    {
+                        Stop();
+                    }
+                });
+
+            _host.Start();
+
+            OnStarted();
+        }
+
+        protected sealed override void OnStop()
+        {
+            _stopRequestedByWindows = true;
+            OnStopping();
+            try
+            {
+                _host.StopAsync().GetAwaiter().GetResult();
+            }
+            finally
+            {
+                _host.Dispose();
+                OnStopped();
+            }
+        }
+
+        /// <summary>
+        /// Executes before ASP.NET Core starts.
+        /// </summary>
+        /// <param name="args">The command line arguments passed to the service.</param>
+        protected virtual void OnStarting(string[] args) { }
+
+        /// <summary>
+        /// Executes after ASP.NET Core starts.
+        /// </summary>
+        protected virtual void OnStarted() { }
+
+        /// <summary>
+        /// Executes before ASP.NET Core shuts down.
+        /// </summary>
+        protected virtual void OnStopping() { }
+
+        /// <summary>
+        /// Executes after ASP.NET Core shuts down.
+        /// </summary>
+        protected virtual void OnStopped() { }
+    }
+}
diff --git a/src/Hosting/WindowsServices/src/WebHostWindowsServiceExtensions.cs b/src/Hosting/WindowsServices/src/WebHostWindowsServiceExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9e657fbe3e3b48c439b908039f0771b9d94e6bba
--- /dev/null
+++ b/src/Hosting/WindowsServices/src/WebHostWindowsServiceExtensions.cs
@@ -0,0 +1,42 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.ServiceProcess;
+
+namespace Microsoft.AspNetCore.Hosting.WindowsServices
+{
+    /// <summary>
+    ///     Extensions to <see cref="IWebHost"/> for hosting inside a Windows service.
+    /// </summary>
+    public static class WebHostWindowsServiceExtensions
+    {
+        /// <summary>
+        ///     Runs the specified web application inside a Windows service and blocks until the service is stopped.
+        /// </summary>
+        /// <param name="host">An instance of the <see cref="IWebHost"/> to host in the Windows service.</param>
+        /// <example>
+        ///     This example shows how to use <see cref="RunAsService"/>.
+        ///     <code>
+        ///         public class Program
+        ///         {
+        ///             public static void Main(string[] args)
+        ///             {
+        ///                 var config = WebHostConfiguration.GetDefault(args);
+        ///                 
+        ///                 var host = new WebHostBuilder()
+        ///                     .UseConfiguration(config)
+        ///                     .Build();
+        ///          
+        ///                 // This call will block until the service is stopped.
+        ///                 host.RunAsService();
+        ///             }
+        ///         }
+        ///     </code>
+        /// </example>
+        public static void RunAsService(this IWebHost host)
+        {
+            var webHostService = new WebHostService(host);
+            ServiceBase.Run(webHostService);
+        }
+    }
+}
diff --git a/src/Hosting/WindowsServices/src/baseline.netcore.json b/src/Hosting/WindowsServices/src/baseline.netcore.json
new file mode 100644
index 0000000000000000000000000000000000000000..a37e8c99eff14a0d438f66c0709825a2c3d6c3c2
--- /dev/null
+++ b/src/Hosting/WindowsServices/src/baseline.netcore.json
@@ -0,0 +1,122 @@
+{
+  "AssemblyIdentity": "Microsoft.AspNetCore.Hosting.WindowsServices, Version=2.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+  "Types": [
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.WindowsServices.WebHostService",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "BaseType": "System.ServiceProcess.ServiceBase",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "OnStart",
+          "Parameters": [
+            {
+              "Name": "args",
+              "Type": "System.String[]"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnStop",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnStarting",
+          "Parameters": [
+            {
+              "Name": "args",
+              "Type": "System.String[]"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnStarted",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnStopping",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnStopped",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "host",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHost"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.WindowsServices.WebHostWindowsServiceExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "RunAsService",
+          "Parameters": [
+            {
+              "Name": "host",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHost"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/Hosting/WindowsServices/src/baseline.netframework.json b/src/Hosting/WindowsServices/src/baseline.netframework.json
new file mode 100644
index 0000000000000000000000000000000000000000..9850e83f45539c8dd8cc6d40ecb50b6d8e51b78a
--- /dev/null
+++ b/src/Hosting/WindowsServices/src/baseline.netframework.json
@@ -0,0 +1,122 @@
+{
+  "AssemblyIdentity": "Microsoft.AspNetCore.Hosting.WindowsServices, Version=2.0.2.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+  "Types": [
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.WindowsServices.WebHostService",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "BaseType": "System.ServiceProcess.ServiceBase",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "OnStart",
+          "Parameters": [
+            {
+              "Name": "args",
+              "Type": "System.String[]"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnStop",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnStarting",
+          "Parameters": [
+            {
+              "Name": "args",
+              "Type": "System.String[]"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnStarted",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnStopping",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnStopped",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "host",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHost"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Hosting.WindowsServices.WebHostWindowsServiceExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "RunAsService",
+          "Parameters": [
+            {
+              "Name": "host",
+              "Type": "Microsoft.AspNetCore.Hosting.IWebHost"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/Hosting/samples/GenericWebHost/FakeServer.cs b/src/Hosting/samples/GenericWebHost/FakeServer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..bb1d66984680793b2c88c2975d22874fd381d617
--- /dev/null
+++ b/src/Hosting/samples/GenericWebHost/FakeServer.cs
@@ -0,0 +1,32 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace GenericWebHost
+{
+    // We can't reference real servers in this sample without creating a circular repo dependency.
+    // This fake server lets us at least run the code.
+    public class FakeServer : IServer
+    {
+        public IFeatureCollection Features => new FeatureCollection();
+
+        public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) => Task.CompletedTask;
+
+        public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+
+        public void Dispose()
+        {
+        }
+    }
+
+    public static class FakeServerWebHostBuilderExtensions
+    {
+        public static IHostBuilder UseFakeServer(this IHostBuilder builder)
+        {
+            return builder.ConfigureServices((builderContext, services) => services.AddSingleton<IServer, FakeServer>());
+        }
+    }
+}
diff --git a/src/Hosting/samples/GenericWebHost/GenericWebHost.csproj b/src/Hosting/samples/GenericWebHost/GenericWebHost.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..74f18791c84cc1ed4cb73408a1896c729af63594
--- /dev/null
+++ b/src/Hosting/samples/GenericWebHost/GenericWebHost.csproj
@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
+    <LangVersion>latest</LangVersion>
+    <SignAssembly>true</SignAssembly>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Hosting" />
+    <Reference Include="Microsoft.Extensions.Hosting" />
+    <Reference Include="Microsoft.Extensions.Configuration.CommandLine" />
+    <Reference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
+    <Reference Include="Microsoft.Extensions.Configuration.Json" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Hosting/samples/GenericWebHost/Program.cs b/src/Hosting/samples/GenericWebHost/Program.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4879031f56097ef75cb66e8e6f031d7fe69d58f2
--- /dev/null
+++ b/src/Hosting/samples/GenericWebHost/Program.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Net;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+
+namespace GenericWebHost
+{
+    public class Program
+    {
+        public static async Task Main(string[] args)
+        {
+            var host = new HostBuilder()
+                .ConfigureAppConfiguration((hostContext, config) =>
+                {
+                    config.AddEnvironmentVariables();
+                    config.AddJsonFile("appsettings.json", optional: true);
+                    config.AddCommandLine(args);
+                })
+                .ConfigureServices((hostContext, services) =>
+                {
+                })
+                .UseFakeServer()
+                .ConfigureWebHost((hostContext, app) =>
+                {
+                    app.Run(async (context) =>
+                    {
+                        await context.Response.WriteAsync("Hello World!");
+                    });
+                })
+                .UseConsoleLifetime()
+                .Build();
+
+            var s = host.Services;
+
+            await host.RunAsync();
+        }
+    }
+}
diff --git a/src/Hosting/samples/GenericWebHost/WebHostExtensions.cs b/src/Hosting/samples/GenericWebHost/WebHostExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..bf5567d81ae68d04178a61f0266ce5e6dd35e779
--- /dev/null
+++ b/src/Hosting/samples/GenericWebHost/WebHostExtensions.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting.Internal;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.ObjectPool;
+
+namespace GenericWebHost
+{
+    public static class WebHostExtensions
+    {
+        public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<HostBuilderContext, IApplicationBuilder> configureApp)
+        {
+            return builder.ConfigureServices((bulderContext, services) =>
+            {
+                services.Configure<WebHostServiceOptions>(options =>
+                {
+                    options.ConfigureApp = configureApp;
+                });
+                services.AddHostedService<WebHostService>();
+                
+                var listener = new DiagnosticListener("Microsoft.AspNetCore");
+                services.AddSingleton<DiagnosticListener>(listener);
+                services.AddSingleton<DiagnosticSource>(listener);
+                
+                services.AddTransient<IHttpContextFactory, HttpContextFactory>();
+                services.AddScoped<IMiddlewareFactory, MiddlewareFactory>();
+
+                // Conjure up a RequestServices
+                services.AddTransient<IStartupFilter, AutoRequestServicesStartupFilter>();
+                services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
+
+                // Ensure object pooling is available everywhere.
+                services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
+            });
+        }
+    }
+}
diff --git a/src/Hosting/samples/GenericWebHost/WebHostService.cs b/src/Hosting/samples/GenericWebHost/WebHostService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1ac316178febf8d025419582b9cd1aae5034178d
--- /dev/null
+++ b/src/Hosting/samples/GenericWebHost/WebHostService.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder.Internal;
+using Microsoft.AspNetCore.Hosting.Internal;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Hosting.Server.Features;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace GenericWebHost
+{
+    internal class WebHostService : IHostedService
+    {
+        public WebHostService(IOptions<WebHostServiceOptions> options, IServiceProvider services, HostBuilderContext hostBuilderContext, IServer server,
+            ILogger<WebHostService> logger, DiagnosticListener diagnosticListener, IHttpContextFactory httpContextFactory)
+        {
+            Options = options?.Value ?? throw new System.ArgumentNullException(nameof(options));
+
+            if (Options.ConfigureApp == null)
+            {
+                throw new ArgumentException(nameof(Options.ConfigureApp));
+            }
+
+            Services = services ?? throw new ArgumentNullException(nameof(services));
+            HostBuilderContext = hostBuilderContext ?? throw new ArgumentNullException(nameof(hostBuilderContext));
+            Server = server ?? throw new ArgumentNullException(nameof(server));
+            Logger = logger ?? throw new ArgumentNullException(nameof(logger));
+            DiagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener));
+            HttpContextFactory = httpContextFactory ?? throw new ArgumentNullException(nameof(httpContextFactory));
+        }
+
+        public WebHostServiceOptions Options { get; }
+        public IServiceProvider Services { get; }
+        public HostBuilderContext HostBuilderContext { get; }
+        public IServer Server { get; }
+        public ILogger<WebHostService> Logger { get; }
+        public DiagnosticListener DiagnosticListener { get; }
+        public IHttpContextFactory HttpContextFactory { get; }
+
+        public Task StartAsync(CancellationToken cancellationToken)
+        {
+            Server.Features.Get<IServerAddressesFeature>()?.Addresses.Add("http://localhost:5000");
+
+            var builder = new ApplicationBuilder(Services, Server.Features);
+            Options.ConfigureApp(HostBuilderContext, builder);
+            var app = builder.Build();
+
+            var httpApp = new HostingApplication(app, Logger, DiagnosticListener, HttpContextFactory);
+            return Server.StartAsync(httpApp, cancellationToken);
+        }
+
+        public Task StopAsync(CancellationToken cancellationToken)
+        {
+            return Server.StopAsync(cancellationToken);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/samples/GenericWebHost/WebHostServiceOptions.cs b/src/Hosting/samples/GenericWebHost/WebHostServiceOptions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..123dcf87907726c092db41630ee3bf5aa6bf9b01
--- /dev/null
+++ b/src/Hosting/samples/GenericWebHost/WebHostServiceOptions.cs
@@ -0,0 +1,11 @@
+using System;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.Hosting;
+
+namespace GenericWebHost
+{
+    public class WebHostServiceOptions
+    {
+        public Action<HostBuilderContext, IApplicationBuilder> ConfigureApp { get; internal set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Hosting/samples/SampleStartups/FakeServer.cs b/src/Hosting/samples/SampleStartups/FakeServer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ebdfdb383eb7fa9ad90cfd41e12d257afc6cbb03
--- /dev/null
+++ b/src/Hosting/samples/SampleStartups/FakeServer.cs
@@ -0,0 +1,32 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace SampleStartups
+{
+    // We can't reference real servers in this sample without creating a circular repo dependency.
+    // This fake server lets us at least run the code.
+    public class FakeServer : IServer
+    {
+        public IFeatureCollection Features => new FeatureCollection();
+
+        public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken) => Task.CompletedTask;
+
+        public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
+
+        public void Dispose()
+        {
+        }
+    }
+
+    public static class FakeServerWebHostBuilderExtensions
+    {
+        public static IWebHostBuilder UseFakeServer(this IWebHostBuilder builder)
+        {
+            return builder.ConfigureServices(services => services.AddSingleton<IServer, FakeServer>());
+        }
+    }
+}
diff --git a/src/Hosting/samples/SampleStartups/SampleStartups.csproj b/src/Hosting/samples/SampleStartups/SampleStartups.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..9c881e56e36e18e505154747e47949d127995ba1
--- /dev/null
+++ b/src/Hosting/samples/SampleStartups/SampleStartups.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
+    <StartupObject>SampleStartups.StartupInjection</StartupObject>
+    <OutputType>exe</OutputType>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Hosting" />
+    <Reference Include="Microsoft.Extensions.Configuration.CommandLine" />
+    <Reference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
+    <Reference Include="Microsoft.Extensions.Configuration.Json" />
+  </ItemGroup>
+</Project>
diff --git a/src/Hosting/samples/SampleStartups/StartupBlockingOnStart.cs b/src/Hosting/samples/SampleStartups/StartupBlockingOnStart.cs
new file mode 100644
index 0000000000000000000000000000000000000000..65a226f3cdee2ce045c6cc46c3bee6b185bb4954
--- /dev/null
+++ b/src/Hosting/samples/SampleStartups/StartupBlockingOnStart.cs
@@ -0,0 +1,47 @@
+using System;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+// Note that this sample will not run. It is only here to illustrate usage patterns.
+
+namespace SampleStartups
+{
+    public class StartupBlockingOnStart : StartupBase
+    {
+        public override void ConfigureServices(IServiceCollection services)
+        {
+            // This method gets called by the runtime. Use this method to add services to the container.
+            // For more information on how to configure your application, visit http://go.microsoft.com/fwlink/?LinkID=398940
+        }
+
+        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+        public override void Configure(IApplicationBuilder app)
+        {
+            app.Run(async (context) =>
+            {
+                await context.Response.WriteAsync("Hello World!");
+            });
+        }
+
+        // Entry point for the application.
+        public static void Main(string[] args)
+        {
+            var config = new ConfigurationBuilder().AddCommandLine(args).Build();
+
+            var host = new WebHostBuilder()
+                .UseConfiguration(config)
+                .UseFakeServer()
+                .UseStartup<StartupBlockingOnStart>()
+                .Build();
+
+            using (host)
+            {
+                host.Start();
+                Console.ReadLine();
+            }
+        }
+    }
+}
diff --git a/src/Hosting/samples/SampleStartups/StartupConfigureAddresses.cs b/src/Hosting/samples/SampleStartups/StartupConfigureAddresses.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8413c47c90ed59e6395e477beefe694830c342b7
--- /dev/null
+++ b/src/Hosting/samples/SampleStartups/StartupConfigureAddresses.cs
@@ -0,0 +1,36 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Configuration;
+
+// Note that this sample will not run. It is only here to illustrate usage patterns.
+
+namespace SampleStartups
+{
+    public class StartupConfigureAddresses : StartupBase
+    {
+        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+        public override void Configure(IApplicationBuilder app)
+        {
+            app.Run(async (context) =>
+            {
+                await context.Response.WriteAsync("Hello World!");
+            });
+        }
+
+        // Entry point for the application.
+        public static void Main(string[] args)
+        {
+            var config = new ConfigurationBuilder().AddCommandLine(args).Build();
+
+            var host = new WebHostBuilder()
+                .UseConfiguration(config)
+                .UseFakeServer()
+                .UseStartup<StartupConfigureAddresses>()
+                .UseUrls("http://localhost:5000", "http://localhost:5001")
+                .Build();
+
+            host.Run();
+        }
+    }
+}
diff --git a/src/Hosting/samples/SampleStartups/StartupExternallyControlled.cs b/src/Hosting/samples/SampleStartups/StartupExternallyControlled.cs
new file mode 100644
index 0000000000000000000000000000000000000000..68ec11c9b06332727fd68b7b1163d3bfe218dfe8
--- /dev/null
+++ b/src/Hosting/samples/SampleStartups/StartupExternallyControlled.cs
@@ -0,0 +1,50 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+
+// Note that this sample will not run. It is only here to illustrate usage patterns.
+
+namespace SampleStartups
+{
+    public class StartupExternallyControlled : StartupBase
+    {
+        private IWebHost _host;
+        private readonly List<string> _urls = new List<string>();
+
+        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+        public override void Configure(IApplicationBuilder app)
+        {
+            app.Run(async (context) =>
+            {
+                await context.Response.WriteAsync("Hello World!");
+            });
+        }
+
+        public StartupExternallyControlled()
+        {
+        }
+
+        public void Start()
+        {
+            _host = new WebHostBuilder()
+                //.UseKestrel()
+                .UseFakeServer()
+                .UseStartup<StartupExternallyControlled>()
+                .Start(_urls.ToArray());
+        }
+
+        public async Task StopAsync()
+        {
+            await _host.StopAsync(TimeSpan.FromSeconds(5));
+            _host.Dispose();
+        }
+
+        public void AddUrl(string url)
+        {
+            _urls.Add(url);
+        }
+    }
+}
diff --git a/src/Hosting/samples/SampleStartups/StartupFullControl.cs b/src/Hosting/samples/SampleStartups/StartupFullControl.cs
new file mode 100644
index 0000000000000000000000000000000000000000..272e747446476793e3cadd20cd5723e3adb507fd
--- /dev/null
+++ b/src/Hosting/samples/SampleStartups/StartupFullControl.cs
@@ -0,0 +1,76 @@
+using System;
+using System.IO;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+
+// Note that this sample will not run. It is only here to illustrate usage patterns.
+
+namespace SampleStartups
+{
+    public class StartupFullControl
+    {
+        public static void Main(string[] args)
+        {
+            var config = new ConfigurationBuilder()
+                .AddCommandLine(args)
+                .AddEnvironmentVariables(prefix: "ASPNETCORE_")
+                .AddJsonFile("hosting.json", optional: true)
+                .Build();
+
+            var host = new WebHostBuilder()
+                .UseConfiguration(config) // Default set of configurations to use, may be subsequently overridden 
+                //.UseKestrel()
+                .UseFakeServer()
+                .UseContentRoot(Directory.GetCurrentDirectory()) // Override the content root with the current directory
+                .UseUrls("http://*:1000", "https://*:902")
+                .UseEnvironment(EnvironmentName.Development)
+                .UseWebRoot("public")
+                .ConfigureServices(services =>
+                {
+                    // Configure services that the application can see
+                    services.AddSingleton<IMyCustomService, MyCustomService>();
+                })
+                .Configure(app =>
+                {
+                    // Write the application inline, this won't call any startup class in the assembly
+
+                    app.Use(next => context =>
+                    {
+                        return next(context);
+                    });
+                })
+                .Build();
+
+            host.Run();
+        }
+    }
+
+    public class MyHostLoggerProvider : ILoggerProvider
+    {
+        public ILogger CreateLogger(string categoryName)
+        {
+            throw new NotImplementedException();
+        }
+
+        public void Dispose()
+        {
+            throw new NotImplementedException();
+        }
+    }
+
+    public interface IMyCustomService
+    {
+        void Go();
+    }
+
+    public class MyCustomService : IMyCustomService
+    {
+        public void Go()
+        {
+            throw new NotImplementedException();
+        }
+    }
+}
diff --git a/src/Hosting/samples/SampleStartups/StartupHelloWorld.cs b/src/Hosting/samples/SampleStartups/StartupHelloWorld.cs
new file mode 100644
index 0000000000000000000000000000000000000000..88a77cfc6c4789c24d2579c978b1da33f7acb9fd
--- /dev/null
+++ b/src/Hosting/samples/SampleStartups/StartupHelloWorld.cs
@@ -0,0 +1,32 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+
+// Note that this sample will not run. It is only here to illustrate usage patterns.
+
+namespace SampleStartups
+{
+    public class StartupHelloWorld : StartupBase
+    {
+        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+        public override void Configure(IApplicationBuilder app)
+        {
+            app.Run(async (context) =>
+            {
+                await context.Response.WriteAsync("Hello World!");
+            });
+        }
+
+        // Entry point for the application.
+        public static void Main(string[] args)
+        {
+            var host = new WebHostBuilder()
+                //.UseKestrel()
+                .UseFakeServer()
+                .UseStartup<StartupHelloWorld>()
+                .Build();
+
+            host.Run();
+        }
+    }
+}
diff --git a/src/Hosting/samples/SampleStartups/StartupInjection.cs b/src/Hosting/samples/SampleStartups/StartupInjection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..381d621a10f0453cc8c85823d43c8ff104ed40c6
--- /dev/null
+++ b/src/Hosting/samples/SampleStartups/StartupInjection.cs
@@ -0,0 +1,69 @@
+using System;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+
+// HostingStartup's in the primary assembly are run automatically.
+[assembly: HostingStartup(typeof(SampleStartups.StartupInjection))]
+
+namespace SampleStartups
+{
+    public class StartupInjection : IHostingStartup
+    {
+        public void Configure(IWebHostBuilder builder)
+        {
+            builder.UseStartup<InjectedStartup>();
+        }
+
+        // Entry point for the application.
+        public static void Main(string[] args)
+        {
+            var host = new WebHostBuilder()
+                //.UseKestrel()
+                .UseFakeServer()
+                // Each of these three sets ApplicationName to the current assembly, which is needed in order to
+                // scan the assembly for HostingStartupAttributes.
+                // .UseSetting(WebHostDefaults.ApplicationKey, "SampleStartups")
+                // .Configure(_ => { })
+                .UseStartup<NormalStartup>()
+                .Build();
+
+            host.Run();
+        }
+    }
+
+    public class NormalStartup
+    {
+        public void ConfigureServices(IServiceCollection services)
+        {
+            Console.WriteLine("NormalStartup.ConfigureServices");
+        }
+
+        public void Configure(IApplicationBuilder app)
+        {
+            Console.WriteLine("NormalStartup.Configure");
+            app.Run(async (context) =>
+            {
+                await context.Response.WriteAsync("Hello World!");
+            });
+        }
+    }
+
+    public class InjectedStartup
+    {
+        public void ConfigureServices(IServiceCollection services)
+        {
+            Console.WriteLine("InjectedStartup.ConfigureServices");
+        }
+
+        public void Configure(IApplicationBuilder app)
+        {
+            Console.WriteLine("InjectedStartup.Configure");
+            app.Run(async (context) =>
+            {
+                await context.Response.WriteAsync("Hello World!");
+            });
+        }
+    }
+}
diff --git a/src/Hosting/test/FunctionalTests/Microsoft.AspNetCore.Hosting.FunctionalTests.csproj b/src/Hosting/test/FunctionalTests/Microsoft.AspNetCore.Hosting.FunctionalTests.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..3daae1c597519cd742895d86f0679673d483b363
--- /dev/null
+++ b/src/Hosting/test/FunctionalTests/Microsoft.AspNetCore.Hosting.FunctionalTests.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>netcoreapp2.0</TargetFrameworks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Content Include="testroot\**\*" CopyToOutputDirectory="PreserveNewest" CopyToPublishDirectory="PreserveNewest" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Server.IntegrationTesting" />
+    <Reference Include="Microsoft.AspNetCore.Hosting" />
+    <Reference Include="Microsoft.Extensions.Logging.Console" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Hosting/test/FunctionalTests/Properties/AssemblyInfo.cs b/src/Hosting/test/FunctionalTests/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a82d7bc1e5c5d3b634c6df188132a8cc436fa594
--- /dev/null
+++ b/src/Hosting/test/FunctionalTests/Properties/AssemblyInfo.cs
@@ -0,0 +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 Xunit;
+
+[assembly: CollectionBehavior(CollectionBehavior.CollectionPerAssembly)]
diff --git a/src/Hosting/test/FunctionalTests/ShutdownTests.cs b/src/Hosting/test/FunctionalTests/ShutdownTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1c229e96b88f3517322835db31b8998a2a7e0212
--- /dev/null
+++ b/src/Hosting/test/FunctionalTests/ShutdownTests.cs
@@ -0,0 +1,144 @@
+// 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.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Server.IntegrationTesting;
+using Microsoft.AspNetCore.Testing;
+using Microsoft.AspNetCore.Testing.xunit;
+using Microsoft.Extensions.Logging.Testing;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.AspNetCore.Hosting.FunctionalTests
+{
+    public class ShutdownTests : LoggedTest
+    {
+        private static readonly string StartedMessage = "Started";
+        private static readonly string CompletionMessage = "Stopping firing\n" +
+                                                            "Stopping end\n" +
+                                                            "Stopped firing\n" +
+                                                            "Stopped end";
+
+        public ShutdownTests(ITestOutputHelper output) : base(output) { }
+
+        [ConditionalFact]
+        [OSSkipCondition(OperatingSystems.Windows)]
+        [OSSkipCondition(OperatingSystems.MacOSX)]
+        public async Task ShutdownTestRun()
+        {
+            await ExecuteShutdownTest(nameof(ShutdownTestRun), "Run");
+        }
+
+        [ConditionalFact(Skip = "https://github.com/aspnet/Hosting/issues/1214")]
+        [OSSkipCondition(OperatingSystems.Windows)]
+        [OSSkipCondition(OperatingSystems.MacOSX)]
+        public async Task ShutdownTestWaitForShutdown()
+        {
+            await ExecuteShutdownTest(nameof(ShutdownTestWaitForShutdown), "WaitForShutdown");
+        }
+
+        private async Task ExecuteShutdownTest(string testName, string shutdownMechanic)
+        {
+            using (StartLog(out var loggerFactory))
+            {
+                var logger = loggerFactory.CreateLogger(testName);
+
+                var applicationPath = Path.Combine(TestPathUtilities.GetSolutionRootDirectory("Hosting"), "test", "TestAssets",
+                    "Microsoft.AspNetCore.Hosting.TestSites");
+
+                var deploymentParameters = new DeploymentParameters(
+                    applicationPath,
+                    ServerType.Kestrel,
+                    RuntimeFlavor.CoreClr,
+                    RuntimeArchitecture.x64)
+                {
+                    EnvironmentName = "Shutdown",
+                    TargetFramework = "netcoreapp2.0",
+                    ApplicationType = ApplicationType.Portable,
+                    PublishApplicationBeforeDeployment = true,
+                    StatusMessagesEnabled = false
+                };
+
+                deploymentParameters.EnvironmentVariables["ASPNETCORE_STARTMECHANIC"] = shutdownMechanic;
+
+                using (var deployer = new SelfHostDeployer(deploymentParameters, loggerFactory))
+                {
+                    await deployer.DeployAsync();
+
+                    var started = new ManualResetEventSlim();
+                    var completed = new ManualResetEventSlim();
+                    var output = string.Empty;
+                    deployer.HostProcess.OutputDataReceived += (sender, args) =>
+                    {
+                        if (!string.IsNullOrEmpty(args.Data) && args.Data.StartsWith(StartedMessage))
+                        {
+                            started.Set();
+                            output += args.Data.Substring(StartedMessage.Length) + '\n';
+                        }
+                        else
+                        {
+                            output += args.Data + '\n';
+                        }
+
+                        if (output.Contains(CompletionMessage))
+                        {
+                            completed.Set();
+                        }
+                    };
+
+                    started.Wait(50000);
+
+                    if (!started.IsSet)
+                    {
+                        throw new InvalidOperationException("Application did not start successfully");
+                    }
+
+                    SendSIGINT(deployer.HostProcess.Id);
+
+                    WaitForExitOrKill(deployer.HostProcess);
+
+                    completed.Wait(50000);
+
+                    if (!started.IsSet)
+                    {
+                        throw new InvalidOperationException($"Application did not write the expected output. The received output is: {output}");
+                    }
+
+                    output = output.Trim('\n');
+
+                    Assert.Equal(CompletionMessage, output);
+                }
+            }
+        }
+
+
+        private static void SendSIGINT(int processId)
+        {
+            var startInfo = new ProcessStartInfo
+            {
+                FileName = "kill",
+                Arguments = processId.ToString(),
+                RedirectStandardOutput = true,
+                UseShellExecute = false
+            };
+
+            var process = Process.Start(startInfo);
+            WaitForExitOrKill(process);
+        }
+
+        private static void WaitForExitOrKill(Process process)
+        {
+            process.WaitForExit(1000);
+            if (!process.HasExited)
+            {
+                process.Kill();
+            }
+
+            Assert.Equal(0, process.ExitCode);
+        }
+    }
+}
diff --git a/src/Hosting/test/FunctionalTests/WebHostBuilderTests.cs b/src/Hosting/test/FunctionalTests/WebHostBuilderTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4a56c432be92df015b097243dab9d7a3961f8594
--- /dev/null
+++ b/src/Hosting/test/FunctionalTests/WebHostBuilderTests.cs
@@ -0,0 +1,73 @@
+// 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;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Server.IntegrationTesting;
+using Microsoft.AspNetCore.Testing;
+using Microsoft.AspNetCore.Testing.xunit;
+using Microsoft.Extensions.Logging.Testing;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace Microsoft.AspNetCore.Hosting.FunctionalTests
+{
+    public class WebHostBuilderTests : LoggedTest
+    {
+        public WebHostBuilderTests(ITestOutputHelper output) : base(output) { }
+
+        [Fact]
+        public async Task InjectedStartup_DefaultApplicationNameIsEntryAssembly_CoreClr()
+            => await InjectedStartup_DefaultApplicationNameIsEntryAssembly(RuntimeFlavor.CoreClr);
+
+        [ConditionalFact]
+        [OSSkipCondition(OperatingSystems.MacOSX)]
+        [OSSkipCondition(OperatingSystems.Linux)]
+        public async Task InjectedStartup_DefaultApplicationNameIsEntryAssembly_Clr()
+            => await InjectedStartup_DefaultApplicationNameIsEntryAssembly(RuntimeFlavor.Clr);
+
+        private async Task InjectedStartup_DefaultApplicationNameIsEntryAssembly(RuntimeFlavor runtimeFlavor)
+        {
+            using (StartLog(out var loggerFactory))
+            {
+                var logger = loggerFactory.CreateLogger(nameof(InjectedStartup_DefaultApplicationNameIsEntryAssembly));
+
+                var applicationPath = Path.Combine(TestPathUtilities.GetSolutionRootDirectory("Hosting"), "test", "TestAssets", "IStartupInjectionAssemblyName");
+
+                var deploymentParameters = new DeploymentParameters(
+                    applicationPath,
+                    ServerType.Kestrel,
+                    runtimeFlavor,
+                    RuntimeArchitecture.x64)
+                {
+                    TargetFramework = runtimeFlavor == RuntimeFlavor.Clr ? "net461" : "netcoreapp2.0",
+                    ApplicationType = ApplicationType.Portable,
+                    StatusMessagesEnabled = false
+                };
+
+                using (var deployer = new SelfHostDeployer(deploymentParameters, loggerFactory))
+                {
+                    await deployer.DeployAsync();
+
+                    string output = string.Empty;
+                    var mre = new ManualResetEventSlim();
+                    deployer.HostProcess.OutputDataReceived += (sender, args) =>
+                    {
+                        if (!string.IsNullOrWhiteSpace(args.Data))
+                        {
+                            output += args.Data + '\n';
+                            mre.Set();
+                        }
+                    };
+
+                    mre.Wait(50000);
+
+                    output = output.Trim('\n');
+
+                    Assert.Equal($"IStartupInjectionAssemblyName", output);
+                }
+            }
+        }
+    }
+}
diff --git a/src/Hosting/test/WebHostBuilderFactory.Tests/Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Tests.csproj b/src/Hosting/test/WebHostBuilderFactory.Tests/Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Tests.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..95412eec3039b9da16db7f54a6c92322820c84ec
--- /dev/null
+++ b/src/Hosting/test/WebHostBuilderFactory.Tests/Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Tests.csproj
@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Include="$(SharedSourceRoot)Hosting.WebHostBuilderFactory\**\*.cs" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\testassets\BuildWebHostPatternTestSite\BuildWebHostPatternTestSite.csproj" />
+    <ProjectReference Include="..\testassets\IStartupInjectionAssemblyName\IStartupInjectionAssemblyName.csproj" />
+    <ProjectReference Include="..\testassets\CreateWebHostBuilderInvalidSignature\CreateWebHostBuilderInvalidSignature.csproj" />
+    <ProjectReference Include="..\testassets\BuildWebHostInvalidSignature\BuildWebHostInvalidSignature.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Hosting/test/WebHostBuilderFactory.Tests/WebHostFactoryResolverTests.cs b/src/Hosting/test/WebHostBuilderFactory.Tests/WebHostFactoryResolverTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f2732f75c48a88629fa824cd794bdfc7040a5aaf
--- /dev/null
+++ b/src/Hosting/test/WebHostBuilderFactory.Tests/WebHostFactoryResolverTests.cs
@@ -0,0 +1,84 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Tests
+{
+    public class WebHostFactoryResolverTests
+    {
+        [Fact]
+        public void CanFindWebHostBuilder_CreateWebHostBuilderPattern()
+        {
+            // Arrange & Act
+            var resolverResult = WebHostFactoryResolver.ResolveWebHostBuilderFactory<IWebHost, IWebHostBuilder>(typeof(IStartupInjectionAssemblyName.Startup).Assembly);
+
+            // Assert
+            Assert.Equal(FactoryResolutionResultKind.Success, resolverResult.ResultKind);
+            Assert.NotNull(resolverResult.WebHostBuilderFactory);
+            Assert.NotNull(resolverResult.WebHostFactory);
+            Assert.IsAssignableFrom<IWebHostBuilder>(resolverResult.WebHostBuilderFactory(Array.Empty<string>()));
+        }
+
+        [Fact]
+        public void CanFindWebHost_CreateWebHostBuilderPattern()
+        {
+            // Arrange & Act
+            var resolverResult = WebHostFactoryResolver.ResolveWebHostFactory<IWebHost, IWebHostBuilder>(typeof(IStartupInjectionAssemblyName.Startup).Assembly);
+
+            // Assert
+            Assert.Equal(FactoryResolutionResultKind.Success, resolverResult.ResultKind);
+            Assert.NotNull(resolverResult.WebHostBuilderFactory);
+            Assert.NotNull(resolverResult.WebHostFactory);
+        }
+
+        [Fact]
+        public void CanNotFindWebHostBuilder_BuildWebHostPattern()
+        {
+            // Arrange & Act
+            var resolverResult = WebHostFactoryResolver.ResolveWebHostBuilderFactory<IWebHost, IWebHostBuilder>(typeof(BuildWebHostPatternTestSite.Startup).Assembly);
+
+            // Assert
+            Assert.Equal(FactoryResolutionResultKind.NoCreateWebHostBuilder, resolverResult.ResultKind);
+            Assert.Null(resolverResult.WebHostBuilderFactory);
+            Assert.Null(resolverResult.WebHostFactory);
+        }
+
+        [Fact]
+        public void CanNotFindWebHostBuilder_CreateWebHostBuilderIncorrectSignature()
+        {
+            // Arrange & Act
+            var resolverResult = WebHostFactoryResolver.ResolveWebHostBuilderFactory<IWebHost, IWebHostBuilder>(typeof(CreateWebHostBuilderInvalidSignature.Startup).Assembly);
+
+            // Assert
+            Assert.Equal(FactoryResolutionResultKind.NoCreateWebHostBuilder, resolverResult.ResultKind);
+            Assert.Null(resolverResult.WebHostBuilderFactory);
+            Assert.Null(resolverResult.WebHostFactory);
+        }
+
+        [Fact]
+        public void CanNotFindWebHost_BuildWebHostIncorrectSignature()
+        {
+            // Arrange & Act
+            var resolverResult = WebHostFactoryResolver.ResolveWebHostFactory<IWebHost, IWebHostBuilder>(typeof(BuildWebHostInvalidSignature.Startup).Assembly);
+
+            // Assert
+            Assert.Equal(FactoryResolutionResultKind.NoBuildWebHost, resolverResult.ResultKind);
+            Assert.Null(resolverResult.WebHostBuilderFactory);
+            Assert.Null(resolverResult.WebHostFactory);
+        }
+
+        [Fact]
+        public void CanFindWebHost_BuildWebHostPattern()
+        {
+            // Arrange & Act
+            var resolverResult = WebHostFactoryResolver.ResolveWebHostFactory<IWebHost, IWebHostBuilder>(typeof(BuildWebHostPatternTestSite.Startup).Assembly);
+
+            // Assert
+            Assert.Equal(FactoryResolutionResultKind.Success, resolverResult.ResultKind);
+            Assert.Null(resolverResult.WebHostBuilderFactory);
+            Assert.NotNull(resolverResult.WebHostFactory);
+        }
+    }
+}
diff --git a/src/Hosting/test/testassets/BuildWebHostInvalidSignature/BuildWebHostInvalidSignature.csproj b/src/Hosting/test/testassets/BuildWebHostInvalidSignature/BuildWebHostInvalidSignature.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..40bd0c87a314e494a7904f2781359d8aec69ab61
--- /dev/null
+++ b/src/Hosting/test/testassets/BuildWebHostInvalidSignature/BuildWebHostInvalidSignature.csproj
@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>netcoreapp2.1;netcoreapp2.0;net461</TargetFrameworks>
+    <OutputType>Exe</OutputType>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Hosting" />
+    <Reference Include="Microsoft.AspNetCore.TestHost" />
+    <Reference Include="Microsoft.Extensions.DependencyInjection" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Hosting/test/testassets/BuildWebHostInvalidSignature/Program.cs b/src/Hosting/test/testassets/BuildWebHostInvalidSignature/Program.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b25cb703ad097af2801763cfa7b42f256667652d
--- /dev/null
+++ b/src/Hosting/test/testassets/BuildWebHostInvalidSignature/Program.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Hosting;
+
+namespace BuildWebHostInvalidSignature
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+        }
+
+        public static IWebHost BuildWebHost() => null;
+    }
+}
diff --git a/src/Hosting/test/testassets/BuildWebHostInvalidSignature/Startup.cs b/src/Hosting/test/testassets/BuildWebHostInvalidSignature/Startup.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0b52bc36b875a15c522229a93e047415d1c38042
--- /dev/null
+++ b/src/Hosting/test/testassets/BuildWebHostInvalidSignature/Startup.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 Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace BuildWebHostInvalidSignature
+{
+    public class Startup
+    {
+        public void ConfigureServices(IServiceCollection services)
+        {
+        }
+
+        public void Configure(IApplicationBuilder builder)
+        {
+        }
+    }
+}
diff --git a/src/Hosting/test/testassets/BuildWebHostPatternTestSite/BuildWebHostPatternTestSite.csproj b/src/Hosting/test/testassets/BuildWebHostPatternTestSite/BuildWebHostPatternTestSite.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..40bd0c87a314e494a7904f2781359d8aec69ab61
--- /dev/null
+++ b/src/Hosting/test/testassets/BuildWebHostPatternTestSite/BuildWebHostPatternTestSite.csproj
@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>netcoreapp2.1;netcoreapp2.0;net461</TargetFrameworks>
+    <OutputType>Exe</OutputType>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Hosting" />
+    <Reference Include="Microsoft.AspNetCore.TestHost" />
+    <Reference Include="Microsoft.Extensions.DependencyInjection" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Hosting/test/testassets/BuildWebHostPatternTestSite/Program.cs b/src/Hosting/test/testassets/BuildWebHostPatternTestSite/Program.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a5a20508b9e09f3f1c4a4c324ab7e22ae349ae0b
--- /dev/null
+++ b/src/Hosting/test/testassets/BuildWebHostPatternTestSite/Program.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Hosting;
+
+namespace BuildWebHostPatternTestSite
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+        }
+
+        public static IWebHost BuildWebHost(string[] args) => null;
+    }
+}
diff --git a/src/Hosting/test/testassets/BuildWebHostPatternTestSite/Startup.cs b/src/Hosting/test/testassets/BuildWebHostPatternTestSite/Startup.cs
new file mode 100644
index 0000000000000000000000000000000000000000..10ecf07a99af3771263b84b39a0eebf37d2fea06
--- /dev/null
+++ b/src/Hosting/test/testassets/BuildWebHostPatternTestSite/Startup.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 Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace BuildWebHostPatternTestSite
+{
+    public class Startup
+    {
+        public void ConfigureServices(IServiceCollection services)
+        {
+        }
+
+        public void Configure(IApplicationBuilder builder)
+        {
+        }
+    }
+}
diff --git a/src/Hosting/test/testassets/CreateWebHostBuilderInvalidSignature/CreateWebHostBuilderInvalidSignature.csproj b/src/Hosting/test/testassets/CreateWebHostBuilderInvalidSignature/CreateWebHostBuilderInvalidSignature.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..40bd0c87a314e494a7904f2781359d8aec69ab61
--- /dev/null
+++ b/src/Hosting/test/testassets/CreateWebHostBuilderInvalidSignature/CreateWebHostBuilderInvalidSignature.csproj
@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>netcoreapp2.1;netcoreapp2.0;net461</TargetFrameworks>
+    <OutputType>Exe</OutputType>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Hosting" />
+    <Reference Include="Microsoft.AspNetCore.TestHost" />
+    <Reference Include="Microsoft.Extensions.DependencyInjection" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Hosting/test/testassets/CreateWebHostBuilderInvalidSignature/Program.cs b/src/Hosting/test/testassets/CreateWebHostBuilderInvalidSignature/Program.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9322836fe0a313cb74bdb7a4f9c0d5782302f804
--- /dev/null
+++ b/src/Hosting/test/testassets/CreateWebHostBuilderInvalidSignature/Program.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Hosting;
+
+namespace CreateWebHostBuilderInvalidSignature
+{
+    class Program
+    {
+        static void Main(string[] args)
+        {
+        }
+
+        public static IWebHostBuilder CreateWebHostBuilder() => null;
+    }
+}
diff --git a/src/Hosting/test/testassets/CreateWebHostBuilderInvalidSignature/Startup.cs b/src/Hosting/test/testassets/CreateWebHostBuilderInvalidSignature/Startup.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2655a7e9374b2aa9f6bc1f555fd607820dad218a
--- /dev/null
+++ b/src/Hosting/test/testassets/CreateWebHostBuilderInvalidSignature/Startup.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 Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace CreateWebHostBuilderInvalidSignature
+{
+    public class Startup
+    {
+        public void ConfigureServices(IServiceCollection services)
+        {
+        }
+
+        public void Configure(IApplicationBuilder builder)
+        {
+        }
+    }
+}
diff --git a/src/Hosting/test/testassets/IStartupInjectionAssemblyName/IStartupInjectionAssemblyName.csproj b/src/Hosting/test/testassets/IStartupInjectionAssemblyName/IStartupInjectionAssemblyName.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..40bd0c87a314e494a7904f2781359d8aec69ab61
--- /dev/null
+++ b/src/Hosting/test/testassets/IStartupInjectionAssemblyName/IStartupInjectionAssemblyName.csproj
@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>netcoreapp2.1;netcoreapp2.0;net461</TargetFrameworks>
+    <OutputType>Exe</OutputType>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Hosting" />
+    <Reference Include="Microsoft.AspNetCore.TestHost" />
+    <Reference Include="Microsoft.Extensions.DependencyInjection" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Hosting/test/testassets/IStartupInjectionAssemblyName/Program.cs b/src/Hosting/test/testassets/IStartupInjectionAssemblyName/Program.cs
new file mode 100644
index 0000000000000000000000000000000000000000..405ec1fba0e2d641070453ff00ce179a134ecbe4
--- /dev/null
+++ b/src/Hosting/test/testassets/IStartupInjectionAssemblyName/Program.cs
@@ -0,0 +1,28 @@
+// 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.Hosting;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace IStartupInjectionAssemblyName
+{
+    public class Program
+    {
+        public static void Main(string[] args)
+        {
+            var webHost = CreateWebHostBuilder(args).Build();
+            var applicationName = webHost.Services.GetRequiredService<IHostingEnvironment>().ApplicationName;
+            Console.WriteLine(applicationName);
+            Console.ReadKey();
+        }
+
+        // Do not change the signature of this method. It's used for tests.
+        private static IWebHostBuilder CreateWebHostBuilder(string [] args) =>
+            new WebHostBuilder()
+            .SuppressStatusMessages(true)
+            .ConfigureServices(services => services.AddSingleton<IStartup, Startup>());
+    }
+}
diff --git a/src/Hosting/test/testassets/IStartupInjectionAssemblyName/Startup.cs b/src/Hosting/test/testassets/IStartupInjectionAssemblyName/Startup.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9f4e27223cedbe53226fe6671a9311c99c3af47a
--- /dev/null
+++ b/src/Hosting/test/testassets/IStartupInjectionAssemblyName/Startup.cs
@@ -0,0 +1,21 @@
+
+using System;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.Http;
+
+namespace IStartupInjectionAssemblyName
+{
+    public class Startup : IStartup
+    {
+        public void Configure(IApplicationBuilder app)
+        {
+        }
+
+        public IServiceProvider ConfigureServices(IServiceCollection services)
+        {
+            return services.BuildServiceProvider();
+        }
+    }
+}
diff --git a/src/Hosting/test/testassets/Microsoft.AspNetCore.Hosting.TestSites/Microsoft.AspNetCore.Hosting.TestSites.csproj b/src/Hosting/test/testassets/Microsoft.AspNetCore.Hosting.TestSites/Microsoft.AspNetCore.Hosting.TestSites.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..7b87b0eed54905d1f82ffdf720b9d8a975f143b3
--- /dev/null
+++ b/src/Hosting/test/testassets/Microsoft.AspNetCore.Hosting.TestSites/Microsoft.AspNetCore.Hosting.TestSites.csproj
@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>netcoreapp2.1;netcoreapp2.0;net461</TargetFrameworks>
+    <OutputType>Exe</OutputType>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Hosting" />
+    <Reference Include="Microsoft.Extensions.Configuration" />
+    <Reference Include="Microsoft.Extensions.Configuration.CommandLine" />
+    <Reference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" />
+    <Reference Include="Microsoft.Extensions.Logging.Console" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Hosting/test/testassets/Microsoft.AspNetCore.Hosting.TestSites/Program.cs b/src/Hosting/test/testassets/Microsoft.AspNetCore.Hosting.TestSites/Program.cs
new file mode 100644
index 0000000000000000000000000000000000000000..36056bff482180dc2e669093c7b8a18d46379861
--- /dev/null
+++ b/src/Hosting/test/testassets/Microsoft.AspNetCore.Hosting.TestSites/Program.cs
@@ -0,0 +1,77 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Console;
+
+namespace ServerComparison.TestSites
+{
+    public static class Program
+    {
+        public static void Main(string[] args)
+        {
+            var config = new ConfigurationBuilder()
+                .AddCommandLine(args)
+                .AddEnvironmentVariables(prefix: "ASPNETCORE_")
+                .Build();
+
+            var builder = new WebHostBuilder()
+                .UseServer(new NoopServer())
+                .UseConfiguration(config)
+                .SuppressStatusMessages(true)
+                .ConfigureLogging((_, factory) =>
+                {
+                    factory.AddConsole();
+                    factory.AddFilter<ConsoleLoggerProvider>(level => level >= LogLevel.Warning);
+                })
+                .UseStartup("Microsoft.AspNetCore.Hosting.TestSites");
+
+            if (config["STARTMECHANIC"] == "Run")
+            {
+                var host = builder.Build();
+
+                host.Run();
+            }
+            else if (config["STARTMECHANIC"] == "WaitForShutdown")
+            {
+                using (var host = builder.Build())
+                {
+                    host.Start();
+
+                    host.WaitForShutdown();
+                }
+            }
+            else
+            {
+                throw new InvalidOperationException("Starting mechanic not specified");
+            }
+        }
+    }
+
+    public class NoopServer : IServer
+    {
+        public void Dispose()
+        {
+        }
+
+        public IFeatureCollection Features { get; } = new FeatureCollection();
+
+        public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
+        {
+            return Task.CompletedTask;
+        }
+
+        public Task StopAsync(CancellationToken cancellationToken)
+        {
+            return Task.CompletedTask;
+        }
+    }
+}
+
diff --git a/src/Hosting/test/testassets/Microsoft.AspNetCore.Hosting.TestSites/StartupShutdown.cs b/src/Hosting/test/testassets/Microsoft.AspNetCore.Hosting.TestSites/StartupShutdown.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8b223d9e5a6fe7a4fcd07144aa14ed02d7db7607
--- /dev/null
+++ b/src/Hosting/test/testassets/Microsoft.AspNetCore.Hosting.TestSites/StartupShutdown.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 Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+
+namespace Microsoft.AspNetCore.Hosting.TestSites
+{
+    public class StartupShutdown
+    {
+        public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory, IApplicationLifetime lifetime)
+        {
+            lifetime.ApplicationStarted.Register(() =>
+            {
+                Console.WriteLine("Started");
+            });
+            lifetime.ApplicationStopping.Register(() =>
+            {
+                Console.WriteLine("Stopping firing");
+                System.Threading.Thread.Sleep(200);
+                Console.WriteLine("Stopping end");
+            });
+            lifetime.ApplicationStopped.Register(() =>
+            {
+                Console.WriteLine("Stopped firing");
+                System.Threading.Thread.Sleep(200);
+                Console.WriteLine("Stopped end");
+            });
+
+            app.Run(context =>
+            {
+                return context.Response.WriteAsync("Hello World");
+            });
+        }
+    }
+}
diff --git a/src/Hosting/test/testassets/TestStartupAssembly1/TestHostingStartup1.cs b/src/Hosting/test/testassets/TestStartupAssembly1/TestHostingStartup1.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e8519c83c36c35cd9d5396fbf97360c6bea8955b
--- /dev/null
+++ b/src/Hosting/test/testassets/TestStartupAssembly1/TestHostingStartup1.cs
@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Hosting;
+
+[assembly: HostingStartup(typeof(TestStartupAssembly1.TestHostingStartup1))]
+
+namespace TestStartupAssembly1
+{
+    public class TestHostingStartup1 : IHostingStartup
+    {
+        public void Configure(IWebHostBuilder builder)
+        {
+            builder.UseSetting("testhostingstartup1", "1");
+            builder.UseSetting("testhostingstartup_chain", builder.GetSetting("testhostingstartup_chain") + "1");
+        }
+    }
+}
diff --git a/src/Hosting/test/testassets/TestStartupAssembly1/TestStartupAssembly1.csproj b/src/Hosting/test/testassets/TestStartupAssembly1/TestStartupAssembly1.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..951d8c69e3bcf3e1f1831835716d1aaca7bd10ce
--- /dev/null
+++ b/src/Hosting/test/testassets/TestStartupAssembly1/TestStartupAssembly1.csproj
@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>netcoreapp2.0;net461</TargetFrameworks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
+  </ItemGroup>
+
+</Project>
\ No newline at end of file
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticateResult.cs b/src/Http/Authentication.Abstractions/src/AuthenticateResult.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5982143bcb9a1abb8fcb1878e0abcb66ce66b2da
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/AuthenticateResult.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.Security.Claims;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Contains the result of an Authenticate call
+    /// </summary>
+    public class AuthenticateResult
+    {
+        protected AuthenticateResult() { }
+
+        /// <summary>
+        /// If a ticket was produced, authenticate was successful.
+        /// </summary>
+        public bool Succeeded => Ticket != null;
+
+        /// <summary>
+        /// The authentication ticket.
+        /// </summary>
+        public AuthenticationTicket Ticket { get; protected set; }
+
+        /// <summary>
+        /// Gets the claims-principal with authenticated user identities.
+        /// </summary>
+        public ClaimsPrincipal Principal => Ticket?.Principal;
+
+        /// <summary>
+        /// Additional state values for the authentication session.
+        /// </summary>
+        public AuthenticationProperties Properties { get; protected set; }
+
+        /// <summary>
+        /// Holds failure information from the authentication.
+        /// </summary>
+        public Exception Failure { get; protected set; }
+
+        /// <summary>
+        /// Indicates that there was no information returned for this authentication scheme.
+        /// </summary>
+        public bool None { get; protected set; }
+
+        /// <summary>
+        /// Indicates that authentication was successful.
+        /// </summary>
+        /// <param name="ticket">The ticket representing the authentication result.</param>
+        /// <returns>The result.</returns>
+        public static AuthenticateResult Success(AuthenticationTicket ticket)
+        {
+            if (ticket == null)
+            {
+                throw new ArgumentNullException(nameof(ticket));
+            }
+            return new AuthenticateResult() { Ticket = ticket, Properties = ticket.Properties };
+        }
+
+        /// <summary>
+        /// Indicates that there was no information returned for this authentication scheme.
+        /// </summary>
+        /// <returns>The result.</returns>
+        public static AuthenticateResult NoResult()
+        {
+            return new AuthenticateResult() { None = true };
+        }
+
+        /// <summary>
+        /// Indicates that there was a failure during authentication.
+        /// </summary>
+        /// <param name="failure">The failure exception.</param>
+        /// <returns>The result.</returns>
+        public static AuthenticateResult Fail(Exception failure)
+        {
+            return new AuthenticateResult() { Failure = failure };
+        }
+
+        /// <summary>
+        /// Indicates that there was a failure during authentication.
+        /// </summary>
+        /// <param name="failure">The failure exception.</param>
+        /// <param name="properties">Additional state values for the authentication session.</param>
+        /// <returns>The result.</returns>
+        public static AuthenticateResult Fail(Exception failure, AuthenticationProperties properties)
+        {
+            return new AuthenticateResult() { Failure = failure, Properties = properties };
+        }
+
+        /// <summary>
+        /// Indicates that there was a failure during authentication.
+        /// </summary>
+        /// <param name="failureMessage">The failure message.</param>
+        /// <returns>The result.</returns>
+        public static AuthenticateResult Fail(string failureMessage)
+            => Fail(new Exception(failureMessage));
+
+        /// <summary>
+        /// Indicates that there was a failure during authentication.
+        /// </summary>
+        /// <param name="failureMessage">The failure message.</param>
+        /// <param name="properties">Additional state values for the authentication session.</param>
+        /// <returns>The result.</returns>
+        public static AuthenticateResult Fail(string failureMessage, AuthenticationProperties properties)
+            => Fail(new Exception(failureMessage), properties);
+    }
+}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationHttpContextExtensions.cs b/src/Http/Authentication.Abstractions/src/AuthenticationHttpContextExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..bb50c6534f5e1fa75e16e97b04303e2b32c7d300
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationHttpContextExtensions.cs
@@ -0,0 +1,197 @@
+// 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.Http;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Extension methods to expose Authentication on HttpContext.
+    /// </summary>
+    public static class AuthenticationHttpContextExtensions
+    {
+        /// <summary>
+        /// Extension method for authenticate using the <see cref="AuthenticationOptions.DefaultAuthenticateScheme"/> scheme.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <returns>The <see cref="AuthenticateResult"/>.</returns>
+        public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context) =>
+            context.AuthenticateAsync(scheme: null);
+
+        /// <summary>
+        /// Extension method for authenticate.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <returns>The <see cref="AuthenticateResult"/>.</returns>
+        public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) =>
+            context.RequestServices.GetRequiredService<IAuthenticationService>().AuthenticateAsync(context, scheme);
+
+        /// <summary>
+        /// Extension method for Challenge.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <returns>The result.</returns>
+        public static Task ChallengeAsync(this HttpContext context, string scheme) =>
+            context.ChallengeAsync(scheme, properties: null);
+
+        /// <summary>
+        /// Extension method for authenticate using the <see cref="AuthenticationOptions.DefaultChallengeScheme"/> scheme.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <returns>The task.</returns>
+        public static Task ChallengeAsync(this HttpContext context) =>
+            context.ChallengeAsync(scheme: null, properties: null);
+
+        /// <summary>
+        /// Extension method for authenticate using the <see cref="AuthenticationOptions.DefaultChallengeScheme"/> scheme.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
+        /// <returns>The task.</returns>
+        public static Task ChallengeAsync(this HttpContext context, AuthenticationProperties properties) =>
+            context.ChallengeAsync(scheme: null, properties: properties);
+
+        /// <summary>
+        /// Extension method for Challenge.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
+        /// <returns>The task.</returns>
+        public static Task ChallengeAsync(this HttpContext context, string scheme, AuthenticationProperties properties) =>
+            context.RequestServices.GetRequiredService<IAuthenticationService>().ChallengeAsync(context, scheme, properties);
+
+        /// <summary>
+        /// Extension method for Forbid.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <returns>The task.</returns>
+        public static Task ForbidAsync(this HttpContext context, string scheme) =>
+            context.ForbidAsync(scheme, properties: null);
+
+        /// <summary>
+        /// Extension method for Forbid using the <see cref="AuthenticationOptions.DefaultForbidScheme"/> scheme..
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <returns>The task.</returns>
+        public static Task ForbidAsync(this HttpContext context) =>
+            context.ForbidAsync(scheme: null, properties: null);
+
+        /// <summary>
+        /// Extension method for Forbid.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
+        /// <returns>The task.</returns>
+        public static Task ForbidAsync(this HttpContext context, AuthenticationProperties properties) =>
+            context.ForbidAsync(scheme: null, properties: properties);
+
+        /// <summary>
+        /// Extension method for Forbid.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
+        /// <returns>The task.</returns>
+        public static Task ForbidAsync(this HttpContext context, string scheme, AuthenticationProperties properties) =>
+            context.RequestServices.GetRequiredService<IAuthenticationService>().ForbidAsync(context, scheme, properties);
+
+        /// <summary>
+        /// Extension method for SignIn.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <param name="principal">The user.</param>
+        /// <returns>The task.</returns>
+        public static Task SignInAsync(this HttpContext context, string scheme, ClaimsPrincipal principal) =>
+            context.SignInAsync(scheme, principal, properties: null);
+
+        /// <summary>
+        /// Extension method for SignIn using the <see cref="AuthenticationOptions.DefaultSignInScheme"/>.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="principal">The user.</param>
+        /// <returns>The task.</returns>
+        public static Task SignInAsync(this HttpContext context, ClaimsPrincipal principal) =>
+            context.SignInAsync(scheme: null, principal: principal, properties: null);
+
+        /// <summary>
+        /// Extension method for SignIn using the <see cref="AuthenticationOptions.DefaultSignInScheme"/>.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="principal">The user.</param>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
+        /// <returns>The task.</returns>
+        public static Task SignInAsync(this HttpContext context, ClaimsPrincipal principal, AuthenticationProperties properties) =>
+            context.SignInAsync(scheme: null, principal: principal, properties: properties);
+
+        /// <summary>
+        /// Extension method for SignIn.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <param name="principal">The user.</param>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
+        /// <returns>The task.</returns>
+        public static Task SignInAsync(this HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties) =>
+            context.RequestServices.GetRequiredService<IAuthenticationService>().SignInAsync(context, scheme, principal, properties);
+
+        /// <summary>
+        /// Extension method for SignOut using the <see cref="AuthenticationOptions.DefaultSignOutScheme"/>.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <returns>The task.</returns>
+        public static Task SignOutAsync(this HttpContext context) => context.SignOutAsync(scheme: null, properties: null);
+
+        /// <summary>
+        /// Extension method for SignOut using the <see cref="AuthenticationOptions.DefaultSignOutScheme"/>.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
+        /// <returns>The task.</returns>
+        public static Task SignOutAsync(this HttpContext context, AuthenticationProperties properties) => context.SignOutAsync(scheme: null, properties: properties);
+
+        /// <summary>
+        /// Extension method for SignOut.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <returns>The task.</returns>
+        public static Task SignOutAsync(this HttpContext context, string scheme) => context.SignOutAsync(scheme, properties: null);
+
+        /// <summary>
+        /// Extension method for SignOut.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
+        /// <returns></returns>
+        public static Task SignOutAsync(this HttpContext context, string scheme, AuthenticationProperties properties) =>
+            context.RequestServices.GetRequiredService<IAuthenticationService>().SignOutAsync(context, scheme, properties);
+
+        /// <summary>
+        /// Extension method for getting the value of an authentication token.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <param name="tokenName">The name of the token.</param>
+        /// <returns>The value of the token.</returns>
+        public static Task<string> GetTokenAsync(this HttpContext context, string scheme, string tokenName) =>
+            context.RequestServices.GetRequiredService<IAuthenticationService>().GetTokenAsync(context, scheme, tokenName);
+
+        /// <summary>
+        /// Extension method for getting the value of an authentication token.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="tokenName">The name of the token.</param>
+        /// <returns>The value of the token.</returns>
+        public static Task<string> GetTokenAsync(this HttpContext context, string tokenName) =>
+            context.RequestServices.GetRequiredService<IAuthenticationService>().GetTokenAsync(context, tokenName);
+    }
+}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationOptions.cs b/src/Http/Authentication.Abstractions/src/AuthenticationOptions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2781a35757060caf3c7ef590e24d296ef03f0d1a
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationOptions.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 Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    public class AuthenticationOptions
+    {
+        private readonly IList<AuthenticationSchemeBuilder> _schemes = new List<AuthenticationSchemeBuilder>();
+
+        /// <summary>
+        /// Returns the schemes in the order they were added (important for request handling priority)
+        /// </summary>
+        public IEnumerable<AuthenticationSchemeBuilder> Schemes => _schemes;
+
+        /// <summary>
+        /// Maps schemes by name.
+        /// </summary>
+        public IDictionary<string, AuthenticationSchemeBuilder> SchemeMap { get; } = new Dictionary<string, AuthenticationSchemeBuilder>(StringComparer.Ordinal);
+
+        /// <summary>
+        /// Adds an <see cref="AuthenticationScheme"/>.
+        /// </summary>
+        /// <param name="name">The name of the scheme being added.</param>
+        /// <param name="configureBuilder">Configures the scheme.</param>
+        public void AddScheme(string name, Action<AuthenticationSchemeBuilder> configureBuilder)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+            if (configureBuilder == null)
+            {
+                throw new ArgumentNullException(nameof(configureBuilder));
+            }
+            if (SchemeMap.ContainsKey(name))
+            {
+                throw new InvalidOperationException("Scheme already exists: " + name);
+            }
+
+            var builder = new AuthenticationSchemeBuilder(name);
+            configureBuilder(builder);
+            _schemes.Add(builder);
+            SchemeMap[name] = builder;
+        }
+
+        /// <summary>
+        /// Adds an <see cref="AuthenticationScheme"/>.
+        /// </summary>
+        /// <typeparam name="THandler">The <see cref="IAuthenticationHandler"/> responsible for the scheme.</typeparam>
+        /// <param name="name">The name of the scheme being added.</param>
+        /// <param name="displayName">The display name for the scheme.</param>
+        public void AddScheme<THandler>(string name, string displayName) where THandler : IAuthenticationHandler
+            => AddScheme(name, b =>
+            {
+                b.DisplayName = displayName;
+                b.HandlerType = typeof(THandler);
+            });
+
+        /// <summary>
+        /// Used as the fallback default scheme for all the other defaults.
+        /// </summary>
+        public string DefaultScheme { get; set; }
+
+        /// <summary>
+        /// Used as the default scheme by <see cref="IAuthenticationService.AuthenticateAsync(HttpContext, string)"/>.
+        /// </summary>
+        public string DefaultAuthenticateScheme { get; set; }
+
+        /// <summary>
+        /// Used as the default scheme by <see cref="IAuthenticationService.SignInAsync(HttpContext, string, System.Security.Claims.ClaimsPrincipal, AuthenticationProperties)"/>.
+        /// </summary>
+        public string DefaultSignInScheme { get; set; }
+
+        /// <summary>
+        /// Used as the default scheme by <see cref="IAuthenticationService.SignOutAsync(HttpContext, string, AuthenticationProperties)"/>.
+        /// </summary>
+        public string DefaultSignOutScheme { get; set; }
+
+        /// <summary>
+        /// Used as the default scheme by <see cref="IAuthenticationService.ChallengeAsync(HttpContext, string, AuthenticationProperties)"/>.
+        /// </summary>
+        public string DefaultChallengeScheme { get; set; }
+
+        /// <summary>
+        /// Used as the default scheme by <see cref="IAuthenticationService.ForbidAsync(HttpContext, string, AuthenticationProperties)"/>.
+        /// </summary>
+        public string DefaultForbidScheme { get; set; }
+    }
+}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationProperties.cs b/src/Http/Authentication.Abstractions/src/AuthenticationProperties.cs
new file mode 100644
index 0000000000000000000000000000000000000000..271329209a13ba5d8fdc02448224dc02b47cd695
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationProperties.cs
@@ -0,0 +1,212 @@
+// 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.Globalization;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Dictionary used to store state values about the authentication session.
+    /// </summary>
+    public class AuthenticationProperties
+    {
+        internal const string IssuedUtcKey = ".issued";
+        internal const string ExpiresUtcKey = ".expires";
+        internal const string IsPersistentKey = ".persistent";
+        internal const string RedirectUriKey = ".redirect";
+        internal const string RefreshKey = ".refresh";
+        internal const string UtcDateTimeFormat = "r";
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AuthenticationProperties"/> class.
+        /// </summary>
+        public AuthenticationProperties()
+            : this(items: null, parameters: null)
+        { }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AuthenticationProperties"/> class.
+        /// </summary>
+        /// <param name="items">State values dictionary to use.</param>
+        public AuthenticationProperties(IDictionary<string, string> items)
+            : this(items, parameters: null)
+        { }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AuthenticationProperties"/> class.
+        /// </summary>
+        /// <param name="items">State values dictionary to use.</param>
+        /// <param name="parameters">Parameters dictionary to use.</param>
+        public AuthenticationProperties(IDictionary<string, string> items, IDictionary<string, object> parameters)
+        {
+            Items = items ?? new Dictionary<string, string>(StringComparer.Ordinal);
+            Parameters = parameters ?? new Dictionary<string, object>(StringComparer.Ordinal);
+        }
+
+        /// <summary>
+        /// State values about the authentication session.
+        /// </summary>
+        public IDictionary<string, string> Items { get; }
+
+        /// <summary>
+        /// Collection of parameters that are passed to the authentication handler. These are not intended for
+        /// serialization or persistence, only for flowing data between call sites.
+        /// </summary>
+        public IDictionary<string, object> Parameters { get; }
+
+        /// <summary>
+        /// Gets or sets whether the authentication session is persisted across multiple requests.
+        /// </summary>
+        public bool IsPersistent
+        {
+            get => GetString(IsPersistentKey) != null;
+            set => SetString(IsPersistentKey, value ? string.Empty : null);
+        }
+
+        /// <summary>
+        /// Gets or sets the full path or absolute URI to be used as an http redirect response value.
+        /// </summary>
+        public string RedirectUri
+        {
+            get => GetString(RedirectUriKey);
+            set => SetString(RedirectUriKey, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the time at which the authentication ticket was issued.
+        /// </summary>
+        public DateTimeOffset? IssuedUtc
+        {
+            get => GetDateTimeOffset(IssuedUtcKey);
+            set => SetDateTimeOffset(IssuedUtcKey, value);
+        }
+
+        /// <summary>
+        /// Gets or sets the time at which the authentication ticket expires.
+        /// </summary>
+        public DateTimeOffset? ExpiresUtc
+        {
+            get => GetDateTimeOffset(ExpiresUtcKey);
+            set => SetDateTimeOffset(ExpiresUtcKey, value);
+        }
+
+        /// <summary>
+        /// Gets or sets if refreshing the authentication session should be allowed.
+        /// </summary>
+        public bool? AllowRefresh
+        {
+            get => GetBool(RefreshKey);
+            set => SetBool(RefreshKey, value);
+        }
+
+        /// <summary>
+        /// Get a string value from the <see cref="Items"/> collection.
+        /// </summary>
+        /// <param name="key">Property key.</param>
+        /// <returns>Retrieved value or <c>null</c> if the property is not set.</returns>
+        public string GetString(string key)
+        {
+            return Items.TryGetValue(key, out string value) ? value : null;
+        }
+
+        /// <summary>
+        /// Set a string value in the <see cref="Items"/> collection.
+        /// </summary>
+        /// <param name="key">Property key.</param>
+        /// <param name="value">Value to set or <c>null</c> to remove the property.</param>
+        public void SetString(string key, string value)
+        {
+            if (value != null)
+            {
+                Items[key] = value;
+            }
+            else if (Items.ContainsKey(key))
+            {
+                Items.Remove(key);
+            }
+        }
+
+        /// <summary>
+        /// Get a parameter from the <see cref="Parameters"/> collection.
+        /// </summary>
+        /// <typeparam name="T">Parameter type.</typeparam>
+        /// <param name="key">Parameter key.</param>
+        /// <returns>Retrieved value or the default value if the property is not set.</returns>
+        public T GetParameter<T>(string key)
+            => Parameters.TryGetValue(key, out var obj) && obj is T value ? value : default;
+
+        /// <summary>
+        /// Set a parameter value in the <see cref="Parameters"/> collection.
+        /// </summary>
+        /// <typeparam name="T">Parameter type.</typeparam>
+        /// <param name="key">Parameter key.</param>
+        /// <param name="value">Value to set.</param>
+        public void SetParameter<T>(string key, T value)
+            => Parameters[key] = value;
+
+        /// <summary>
+        /// Get a bool value from the <see cref="Items"/> collection.
+        /// </summary>
+        /// <param name="key">Property key.</param>
+        /// <returns>Retrieved value or <c>null</c> if the property is not set.</returns>
+        protected bool? GetBool(string key)
+        {
+            if (Items.TryGetValue(key, out string value) && bool.TryParse(value, out bool boolValue))
+            {
+                return boolValue;
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Set a bool value in the <see cref="Items"/> collection.
+        /// </summary>
+        /// <param name="key">Property key.</param>
+        /// <param name="value">Value to set or <c>null</c> to remove the property.</param>
+        protected void SetBool(string key, bool? value)
+        {
+            if (value.HasValue)
+            {
+                Items[key] = value.Value.ToString();
+            }
+            else if (Items.ContainsKey(key))
+            {
+                Items.Remove(key);
+            }
+        }
+
+        /// <summary>
+        /// Get a DateTimeOffset value from the <see cref="Items"/> collection.
+        /// </summary>
+        /// <param name="key">Property key.</param>
+        /// <returns>Retrieved value or <c>null</c> if the property is not set.</returns>
+        protected DateTimeOffset? GetDateTimeOffset(string key)
+        {
+            if (Items.TryGetValue(key, out string value)
+                && DateTimeOffset.TryParseExact(value, UtcDateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTimeOffset dateTimeOffset))
+            {
+                return dateTimeOffset;
+            }
+            return null;
+        }
+
+        /// <summary>
+        /// Set a DateTimeOffset value in the <see cref="Items"/> collection.
+        /// </summary>
+        /// <param name="key">Property key.</param>
+        /// <param name="value">Value to set or <c>null</c> to remove the property.</param>
+        protected void SetDateTimeOffset(string key, DateTimeOffset? value)
+        {
+            if (value.HasValue)
+            {
+                Items[key] = value.Value.ToString(UtcDateTimeFormat, CultureInfo.InvariantCulture);
+            }
+            else if (Items.ContainsKey(key))
+            {
+                Items.Remove(key);
+            }
+        }
+    }
+}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationScheme.cs b/src/Http/Authentication.Abstractions/src/AuthenticationScheme.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a72dc893edd39d4033653493c4196a40b69744ee
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationScheme.cs
@@ -0,0 +1,56 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Reflection;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// AuthenticationSchemes assign a name to a specific <see cref="IAuthenticationHandler"/>
+    /// handlerType.
+    /// </summary>
+    public class AuthenticationScheme
+    {
+        /// <summary>
+        /// Constructor.
+        /// </summary>
+        /// <param name="name">The name for the authentication scheme.</param>
+        /// <param name="displayName">The display name for the authentication scheme.</param>
+        /// <param name="handlerType">The <see cref="IAuthenticationHandler"/> type that handles this scheme.</param>
+        public AuthenticationScheme(string name, string displayName, Type handlerType)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+            if (handlerType == null)
+            {
+                throw new ArgumentNullException(nameof(handlerType));
+            }
+            if (!typeof(IAuthenticationHandler).IsAssignableFrom(handlerType))
+            {
+                throw new ArgumentException("handlerType must implement IAuthenticationHandler.");
+            }
+
+            Name = name;
+            HandlerType = handlerType;
+            DisplayName = displayName;
+        }
+
+        /// <summary>
+        /// The name of the authentication scheme.
+        /// </summary>
+        public string Name { get; }
+
+        /// <summary>
+        /// The display name for the scheme. Null is valid and used for non user facing schemes.
+        /// </summary>
+        public string DisplayName { get; }
+
+        /// <summary>
+        /// The <see cref="IAuthenticationHandler"/> type that handles this scheme.
+        /// </summary>
+        public Type HandlerType { get; }
+    }
+}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationSchemeBuilder.cs b/src/Http/Authentication.Abstractions/src/AuthenticationSchemeBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..30e843c02820530f1afe407ac9a238f5004c2d04
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationSchemeBuilder.cs
@@ -0,0 +1,43 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Used to build <see cref="AuthenticationScheme"/>s.
+    /// </summary>
+    public class AuthenticationSchemeBuilder
+    {
+        /// <summary>
+        /// Constructor.
+        /// </summary>
+        /// <param name="name">The name of the scheme being built.</param>
+        public AuthenticationSchemeBuilder(string name)
+        {
+            Name = name;
+        }
+
+        /// <summary>
+        /// The name of the scheme being built.
+        /// </summary>
+        public string Name { get; }
+
+        /// <summary>
+        /// The display name for the scheme being built.
+        /// </summary>
+        public string DisplayName { get; set; }
+
+        /// <summary>
+        /// The <see cref="IAuthenticationHandler"/> type responsible for this scheme.
+        /// </summary>
+        public Type HandlerType { get; set; }
+
+        /// <summary>
+        /// Builds the <see cref="AuthenticationScheme"/> instance.
+        /// </summary>
+        /// <returns></returns>
+        public AuthenticationScheme Build() => new AuthenticationScheme(Name, DisplayName, HandlerType);
+    }
+}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationTicket.cs b/src/Http/Authentication.Abstractions/src/AuthenticationTicket.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c31f15ec014423f9c259b8a6ebd2695174fc0484
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationTicket.cs
@@ -0,0 +1,56 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Security.Claims;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Contains user identity information as well as additional authentication state.
+    /// </summary>
+    public class AuthenticationTicket
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AuthenticationTicket"/> class
+        /// </summary>
+        /// <param name="principal">the <see cref="ClaimsPrincipal"/> that represents the authenticated user.</param>
+        /// <param name="properties">additional properties that can be consumed by the user or runtime.</param>
+        /// <param name="authenticationScheme">the authentication middleware that was responsible for this ticket.</param>
+        public AuthenticationTicket(ClaimsPrincipal principal, AuthenticationProperties properties, string authenticationScheme)
+        {
+            if (principal == null)
+            {
+                throw new ArgumentNullException(nameof(principal));
+            }
+
+            AuthenticationScheme = authenticationScheme;
+            Principal = principal;
+            Properties = properties ?? new AuthenticationProperties();
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AuthenticationTicket"/> class
+        /// </summary>
+        /// <param name="principal">the <see cref="ClaimsPrincipal"/> that represents the authenticated user.</param>
+        /// <param name="authenticationScheme">the authentication middleware that was responsible for this ticket.</param>
+        public AuthenticationTicket(ClaimsPrincipal principal, string authenticationScheme) 
+            : this(principal, properties: null, authenticationScheme: authenticationScheme)
+        { }
+
+        /// <summary>
+        /// Gets the authentication type.
+        /// </summary>
+        public string AuthenticationScheme { get; private set; }
+
+        /// <summary>
+        /// Gets the claims-principal with authenticated user identities.
+        /// </summary>
+        public ClaimsPrincipal Principal { get; private set; }
+
+        /// <summary>
+        /// Additional state values for the authentication session.
+        /// </summary>
+        public AuthenticationProperties Properties { get; private set; }
+    }
+}
diff --git a/src/Http/Authentication.Abstractions/src/AuthenticationToken.cs b/src/Http/Authentication.Abstractions/src/AuthenticationToken.cs
new file mode 100644
index 0000000000000000000000000000000000000000..555da9e098d452aea6f7994ca33fc918d29c1a37
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/AuthenticationToken.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.
+
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Name/Value representing an token.
+    /// </summary>
+    public class AuthenticationToken
+    {
+        /// <summary>
+        /// Name.
+        /// </summary>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// Value.
+        /// </summary>
+        public string Value { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Authentication.Abstractions/src/IAuthenticationFeature.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..43e5a13b4947fd5c78977bbb50da1facd4391e6c
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationFeature.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Used to capture path info so redirects can be computed properly within an app.Map().
+    /// </summary>
+    public interface IAuthenticationFeature
+    {
+        /// <summary>
+        /// The original path base.
+        /// </summary>
+        PathString OriginalPathBase { get; set; }
+
+        /// <summary>
+        /// The original path.
+        /// </summary>
+        PathString OriginalPath { get; set; }
+    }
+}
diff --git a/src/Http/Authentication.Abstractions/src/IAuthenticationHandler.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationHandler.cs
new file mode 100644
index 0000000000000000000000000000000000000000..aeb373e18e7c381cef5d89f80ad2346ec38a724c
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationHandler.cs
@@ -0,0 +1,42 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Created per request to handle authentication for to a particular scheme.
+    /// </summary>
+    public interface IAuthenticationHandler
+    {
+        /// <summary>
+        /// The handler should initialize anything it needs from the request and scheme here.
+        /// </summary>
+        /// <param name="scheme">The <see cref="AuthenticationScheme"/> scheme.</param>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <returns></returns>
+        Task InitializeAsync(AuthenticationScheme scheme, HttpContext context);
+
+        /// <summary>
+        /// Authentication behavior.
+        /// </summary>
+        /// <returns>The <see cref="AuthenticateResult"/> result.</returns>
+        Task<AuthenticateResult> AuthenticateAsync();
+
+        /// <summary>
+        /// Challenge behavior.
+        /// </summary>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/> that contains the extra meta-data arriving with the authentication.</param>
+        /// <returns>A task.</returns>
+        Task ChallengeAsync(AuthenticationProperties properties);
+
+        /// <summary>
+        /// Forbid behavior.
+        /// </summary>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/> that contains the extra meta-data arriving with the authentication.</param>
+        /// <returns>A task.</returns>
+        Task ForbidAsync(AuthenticationProperties properties);
+    }
+}
diff --git a/src/Http/Authentication.Abstractions/src/IAuthenticationHandlerProvider.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationHandlerProvider.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0507f51d611dcc6b5c353143fdfa799b6e0a5ca9
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationHandlerProvider.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 System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Provides the appropriate IAuthenticationHandler instance for the authenticationScheme and request.
+    /// </summary>
+    public interface IAuthenticationHandlerProvider
+    {
+        /// <summary>
+        /// Returns the handler instance that will be used.
+        /// </summary>
+        /// <param name="context">The context.</param>
+        /// <param name="authenticationScheme">The name of the authentication scheme being handled.</param>
+        /// <returns>The handler instance.</returns>
+        Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context, string authenticationScheme);
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Authentication.Abstractions/src/IAuthenticationRequestHandler.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationRequestHandler.cs
new file mode 100644
index 0000000000000000000000000000000000000000..fb1b227ad7a0ca2933b8bfacb278c369516e6cef
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationRequestHandler.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 System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Used to determine if a handler wants to participate in request processing.
+    /// </summary>
+    public interface IAuthenticationRequestHandler : IAuthenticationHandler
+    {
+        /// <summary>
+        /// Returns true if request processing should stop.
+        /// </summary>
+        /// <returns></returns>
+        Task<bool> HandleRequestAsync();
+    }
+
+}
diff --git a/src/Http/Authentication.Abstractions/src/IAuthenticationSchemeProvider.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationSchemeProvider.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3d2584fca859e0acef9fbb242b5cc3c855c9d1d1
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationSchemeProvider.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.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Responsible for managing what authenticationSchemes are supported.
+    /// </summary>
+    public interface IAuthenticationSchemeProvider
+    {
+        /// <summary>
+        /// Returns all currently registered <see cref="AuthenticationScheme"/>s.
+        /// </summary>
+        /// <returns>All currently registered <see cref="AuthenticationScheme"/>s.</returns>
+        Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync();
+
+        /// <summary>
+        /// Returns the <see cref="AuthenticationScheme"/> matching the name, or null.
+        /// </summary>
+        /// <param name="name">The name of the authenticationScheme.</param>
+        /// <returns>The scheme or null if not found.</returns>
+        Task<AuthenticationScheme> GetSchemeAsync(string name);
+
+        /// <summary>
+        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.AuthenticateAsync(HttpContext, string)"/>.
+        /// This is typically specified via <see cref="AuthenticationOptions.DefaultAuthenticateScheme"/>.
+        /// Otherwise, this will fallback to <see cref="AuthenticationOptions.DefaultScheme"/>.
+        /// </summary>
+        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.AuthenticateAsync(HttpContext, string)"/>.</returns>
+        Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync();
+
+        /// <summary>
+        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.ChallengeAsync(HttpContext, string, AuthenticationProperties)"/>.
+        /// This is typically specified via <see cref="AuthenticationOptions.DefaultChallengeScheme"/>.
+        /// Otherwise, this will fallback to <see cref="AuthenticationOptions.DefaultScheme"/>.
+        /// </summary>
+        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.ChallengeAsync(HttpContext, string, AuthenticationProperties)"/>.</returns>
+        Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync();
+
+        /// <summary>
+        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.ForbidAsync(HttpContext, string, AuthenticationProperties)"/>.
+        /// This is typically specified via <see cref="AuthenticationOptions.DefaultForbidScheme"/>.
+        /// Otherwise, this will fallback to <see cref="GetDefaultChallengeSchemeAsync"/> .
+        /// </summary>
+        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.ForbidAsync(HttpContext, string, AuthenticationProperties)"/>.</returns>
+        Task<AuthenticationScheme> GetDefaultForbidSchemeAsync();
+
+        /// <summary>
+        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.SignInAsync(HttpContext, string, System.Security.Claims.ClaimsPrincipal, AuthenticationProperties)"/>.
+        /// This is typically specified via <see cref="AuthenticationOptions.DefaultSignInScheme"/>.
+        /// Otherwise, this will fallback to <see cref="AuthenticationOptions.DefaultScheme"/>.
+        /// </summary>
+        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.SignInAsync(HttpContext, string, System.Security.Claims.ClaimsPrincipal, AuthenticationProperties)"/>.</returns>
+        Task<AuthenticationScheme> GetDefaultSignInSchemeAsync();
+
+        /// <summary>
+        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.SignOutAsync(HttpContext, string, AuthenticationProperties)"/>.
+        /// This is typically specified via <see cref="AuthenticationOptions.DefaultSignOutScheme"/>.
+        /// Otherwise, this will fallback to <see cref="GetDefaultSignInSchemeAsync"/> .
+        /// </summary>
+        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.SignOutAsync(HttpContext, string, AuthenticationProperties)"/>.</returns>
+        Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync();
+
+        /// <summary>
+        /// Registers a scheme for use by <see cref="IAuthenticationService"/>. 
+        /// </summary>
+        /// <param name="scheme">The scheme.</param>
+        void AddScheme(AuthenticationScheme scheme);
+
+        /// <summary>
+        /// Removes a scheme, preventing it from being used by <see cref="IAuthenticationService"/>.
+        /// </summary>
+        /// <param name="name">The name of the authenticationScheme being removed.</param>
+        void RemoveScheme(string name);
+
+        /// <summary>
+        /// Returns the schemes in priority order for request handling.
+        /// </summary>
+        /// <returns>The schemes in priority order for request handling</returns>
+        Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync();
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Authentication.Abstractions/src/IAuthenticationService.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e5d53360160ebd19d2027fe6da419699af4e3ff6
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationService.cs
@@ -0,0 +1,60 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Used to provide authentication.
+    /// </summary>
+    public interface IAuthenticationService
+    {
+        /// <summary>
+        /// Authenticate for the specified authentication scheme.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/>.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <returns>The result.</returns>
+        Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme);
+
+        /// <summary>
+        /// Challenge the specified authentication scheme.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/>.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/>.</param>
+        /// <returns>A task.</returns>
+        Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties);
+
+        /// <summary>
+        /// Forbids the specified authentication scheme.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/>.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/>.</param>
+        /// <returns>A task.</returns>
+        Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties);
+
+        /// <summary>
+        /// Sign a principal in for the specified authentication scheme.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/>.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <param name="principal">The <see cref="ClaimsPrincipal"/> to sign in.</param>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/>.</param>
+        /// <returns>A task.</returns>
+        Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties);
+
+        /// <summary>
+        /// Sign out the specified authentication scheme.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/>.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/>.</param>
+        /// <returns>A task.</returns>
+        Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties);
+    }
+}
diff --git a/src/Http/Authentication.Abstractions/src/IAuthenticationSignInHandler.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationSignInHandler.cs
new file mode 100644
index 0000000000000000000000000000000000000000..69b88032d5adecc7e93742b130edf28d5677a507
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationSignInHandler.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 System.Security.Claims;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Used to determine if a handler supports SignIn.
+    /// </summary>
+    public interface IAuthenticationSignInHandler : IAuthenticationSignOutHandler
+    {
+        /// <summary>
+        /// Handle sign in.
+        /// </summary>
+        /// <param name="user">The <see cref="ClaimsPrincipal"/> user.</param>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/> that contains the extra meta-data arriving with the authentication.</param>
+        /// <returns>A task.</returns>
+        Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties);
+    }
+}
diff --git a/src/Http/Authentication.Abstractions/src/IAuthenticationSignOutHandler.cs b/src/Http/Authentication.Abstractions/src/IAuthenticationSignOutHandler.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f76d116a7602b4281102cc5b226261b03e7a2215
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/IAuthenticationSignOutHandler.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.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Used to determine if a handler supports SignOut.
+    /// </summary>
+    public interface IAuthenticationSignOutHandler : IAuthenticationHandler
+    {
+        /// <summary>
+        /// Signout behavior.
+        /// </summary>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/> that contains the extra meta-data arriving with the authentication.</param>
+        /// <returns>A task.</returns>
+        Task SignOutAsync(AuthenticationProperties properties);
+    }
+
+}
diff --git a/src/Http/Authentication.Abstractions/src/IClaimsTransformation.cs b/src/Http/Authentication.Abstractions/src/IClaimsTransformation.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0193d957838c58c759ac078c15add3b28bba00f4
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/IClaimsTransformation.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Security.Claims;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Used by the <see cref="IAuthenticationService"/> for claims transformation.
+    /// </summary>
+    public interface IClaimsTransformation
+    {
+        /// <summary>
+        /// Provides a central transformation point to change the specified principal. 
+        /// Note: this will be run on each AuthenticateAsync call, so its safer to
+        /// return a new ClaimsPrincipal if your transformation is not idempotent.
+        /// </summary>
+        /// <param name="principal">The <see cref="ClaimsPrincipal"/> to transform.</param>
+        /// <returns>The transformed principal.</returns>
+        Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal);
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Authentication.Abstractions/src/Microsoft.AspNetCore.Authentication.Abstractions.csproj b/src/Http/Authentication.Abstractions/src/Microsoft.AspNetCore.Authentication.Abstractions.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..bfb6e8e9edd25e7dccc2987ae9e56b92dcf03695
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/Microsoft.AspNetCore.Authentication.Abstractions.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0">
+  <PropertyGroup>
+    <Description>ASP.NET Core common types used by the various authentication components.</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <NoWarn>$(NoWarn);CS1591</NoWarn>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore;authentication;security</PackageTags>
+    <EnableApiCheck>false</EnableApiCheck>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Http.Abstractions" />
+    <Reference Include="Microsoft.Extensions.Logging.Abstractions" />
+    <Reference Include="Microsoft.Extensions.Options" />
+  </ItemGroup>
+
+</Project>
\ No newline at end of file
diff --git a/src/Http/Authentication.Abstractions/src/TokenExtensions.cs b/src/Http/Authentication.Abstractions/src/TokenExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..497acabc23316c64bd54215b68e709231cb7a375
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/TokenExtensions.cs
@@ -0,0 +1,161 @@
+// 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.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Extension methods for storing authentication tokens in <see cref="AuthenticationProperties"/>.
+    /// </summary>
+    public static class AuthenticationTokenExtensions
+    {
+        private static string TokenNamesKey = ".TokenNames";
+        private static string TokenKeyPrefix = ".Token.";
+
+        /// <summary>
+        /// Stores a set of authentication tokens, after removing any old tokens.
+        /// </summary>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
+        /// <param name="tokens">The tokens to store.</param>
+        public static void StoreTokens(this AuthenticationProperties properties, IEnumerable<AuthenticationToken> tokens)
+        {
+            if (properties == null)
+            {
+                throw new ArgumentNullException(nameof(properties));
+            }
+            if (tokens == null)
+            {
+                throw new ArgumentNullException(nameof(tokens));
+            }
+
+            // Clear old tokens first
+            var oldTokens = properties.GetTokens();
+            foreach (var t in oldTokens)
+            {
+                properties.Items.Remove(TokenKeyPrefix + t.Name);
+            }
+            properties.Items.Remove(TokenNamesKey);
+
+            var tokenNames = new List<string>();
+            foreach (var token in tokens)
+            {
+                // REVIEW: should probably check that there are no ; in the token name and throw or encode
+                tokenNames.Add(token.Name);
+                properties.Items[TokenKeyPrefix+token.Name] = token.Value;
+            }
+            if (tokenNames.Count > 0)
+            {
+                properties.Items[TokenNamesKey] = string.Join(";", tokenNames.ToArray());
+            }
+        }
+
+        /// <summary>
+        /// Returns the value of a token.
+        /// </summary>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
+        /// <param name="tokenName">The token name.</param>
+        /// <returns>The token value.</returns>
+        public static string GetTokenValue(this AuthenticationProperties properties, string tokenName)
+        {
+            if (properties == null)
+            {
+                throw new ArgumentNullException(nameof(properties));
+            }
+            if (tokenName == null)
+            {
+                throw new ArgumentNullException(nameof(tokenName));
+            }
+
+            var tokenKey = TokenKeyPrefix + tokenName;
+            return properties.Items.ContainsKey(tokenKey)
+                ? properties.Items[tokenKey]
+                : null;
+        }
+
+        public static bool UpdateTokenValue(this AuthenticationProperties properties, string tokenName, string tokenValue)
+        {
+            if (properties == null)
+            {
+                throw new ArgumentNullException(nameof(properties));
+            }
+            if (tokenName == null)
+            {
+                throw new ArgumentNullException(nameof(tokenName));
+            }
+
+            var tokenKey = TokenKeyPrefix + tokenName;
+            if (!properties.Items.ContainsKey(tokenKey))
+            {
+                return false;
+            }
+            properties.Items[tokenKey] = tokenValue;
+            return true;
+        }
+
+        /// <summary>
+        /// Returns all of the AuthenticationTokens contained in the properties.
+        /// </summary>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/> properties.</param>
+        /// <returns>The authentication toekns.</returns>
+        public static IEnumerable<AuthenticationToken> GetTokens(this AuthenticationProperties properties)
+        {
+            if (properties == null)
+            {
+                throw new ArgumentNullException(nameof(properties));
+            }
+
+            var tokens = new List<AuthenticationToken>();
+            if (properties.Items.ContainsKey(TokenNamesKey))
+            {
+                var tokenNames = properties.Items[TokenNamesKey].Split(';');
+                foreach (var name in tokenNames)
+                {
+                    var token = properties.GetTokenValue(name);
+                    if (token != null)
+                    {
+                        tokens.Add(new AuthenticationToken { Name = name, Value = token });
+                    }
+                }
+            }
+
+            return tokens;
+        }
+
+        /// <summary>
+        /// Extension method for getting the value of an authentication token.
+        /// </summary>
+        /// <param name="auth">The <see cref="IAuthenticationService"/>.</param>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="tokenName">The name of the token.</param>
+        /// <returns>The value of the token.</returns>
+        public static Task<string> GetTokenAsync(this IAuthenticationService auth, HttpContext context, string tokenName) 
+            => auth.GetTokenAsync(context, scheme: null, tokenName: tokenName);
+
+        /// <summary>
+        /// Extension method for getting the value of an authentication token.
+        /// </summary>
+        /// <param name="auth">The <see cref="IAuthenticationService"/>.</param>
+        /// <param name="context">The <see cref="HttpContext"/> context.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <param name="tokenName">The name of the token.</param>
+        /// <returns>The value of the token.</returns>
+        public static async Task<string> GetTokenAsync(this IAuthenticationService auth, HttpContext context, string scheme, string tokenName)
+        {
+            if (auth == null)
+            {
+                throw new ArgumentNullException(nameof(auth));
+            }
+            if (tokenName == null)
+            {
+                throw new ArgumentNullException(nameof(tokenName));
+            }
+
+            var result = await auth.AuthenticateAsync(context, scheme);
+            return result?.Properties?.GetTokenValue(tokenName);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Authentication.Abstractions/src/baseline.netcore.json b/src/Http/Authentication.Abstractions/src/baseline.netcore.json
new file mode 100644
index 0000000000000000000000000000000000000000..2d1e7e00e4d8508bda90a74d5008faea579b65f8
--- /dev/null
+++ b/src/Http/Authentication.Abstractions/src/baseline.netcore.json
@@ -0,0 +1,1734 @@
+{
+  "AssemblyIdentity": "Microsoft.AspNetCore.Authentication.Abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+  "Types": [
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.AuthenticateResult",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Succeeded",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Ticket",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticationTicket",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Ticket",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationTicket"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Principal",
+          "Parameters": [],
+          "ReturnType": "System.Security.Claims.ClaimsPrincipal",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Properties",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticationProperties",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Failure",
+          "Parameters": [],
+          "ReturnType": "System.Exception",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Failure",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Exception"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_None",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_None",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Success",
+          "Parameters": [
+            {
+              "Name": "ticket",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationTicket"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticateResult",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "NoResult",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticateResult",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Fail",
+          "Parameters": [
+            {
+              "Name": "failure",
+              "Type": "System.Exception"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticateResult",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Fail",
+          "Parameters": [
+            {
+              "Name": "failureMessage",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticateResult",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Protected",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "AuthenticateAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.AuthenticateResult>",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AuthenticateAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.AuthenticateResult>",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ChallengeAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ChallengeAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ChallengeAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ChallengeAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ForbidAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ForbidAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ForbidAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ForbidAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignInAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "principal",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignInAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "principal",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignInAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "principal",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignInAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "principal",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignOutAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignOutAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignOutAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignOutAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetTokenAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "tokenName",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.String>",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetTokenAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "tokenName",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.String>",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.AuthenticationOptions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Schemes",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Authentication.AuthenticationSchemeBuilder>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_SchemeMap",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.String, Microsoft.AspNetCore.Authentication.AuthenticationSchemeBuilder>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AddScheme",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            },
+            {
+              "Name": "configureBuilder",
+              "Type": "System.Action<Microsoft.AspNetCore.Authentication.AuthenticationSchemeBuilder>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AddScheme<T0>",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            },
+            {
+              "Name": "displayName",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "THandler",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": [
+                "Microsoft.AspNetCore.Authentication.IAuthenticationHandler"
+              ]
+            }
+          ]
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_DefaultScheme",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_DefaultScheme",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_DefaultAuthenticateScheme",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_DefaultAuthenticateScheme",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_DefaultSignInScheme",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_DefaultSignInScheme",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_DefaultSignOutScheme",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_DefaultSignOutScheme",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_DefaultChallengeScheme",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_DefaultChallengeScheme",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_DefaultForbidScheme",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_DefaultForbidScheme",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.AuthenticationProperties",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Items",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.String, System.String>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IsPersistent",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_IsPersistent",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_RedirectUri",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RedirectUri",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IssuedUtc",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_IssuedUtc",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.DateTimeOffset>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ExpiresUtc",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ExpiresUtc",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.DateTimeOffset>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_AllowRefresh",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Boolean>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_AllowRefresh",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.Boolean>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "items",
+              "Type": "System.Collections.Generic.IDictionary<System.String, System.String>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.AuthenticationScheme",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Name",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_DisplayName",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HandlerType",
+          "Parameters": [],
+          "ReturnType": "System.Type",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            },
+            {
+              "Name": "displayName",
+              "Type": "System.String"
+            },
+            {
+              "Name": "handlerType",
+              "Type": "System.Type"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.AuthenticationSchemeBuilder",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Name",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_DisplayName",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_DisplayName",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HandlerType",
+          "Parameters": [],
+          "ReturnType": "System.Type",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_HandlerType",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Type"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Build",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticationScheme",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.AuthenticationTicket",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_AuthenticationScheme",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Principal",
+          "Parameters": [],
+          "ReturnType": "System.Security.Claims.ClaimsPrincipal",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Properties",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticationProperties",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "principal",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            },
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "principal",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            },
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.AuthenticationToken",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Name",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Name",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Value",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Value",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.IAuthenticationFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_OriginalPathBase",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_OriginalPathBase",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_OriginalPath",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_OriginalPath",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.IAuthenticationHandler",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "InitializeAsync",
+          "Parameters": [
+            {
+              "Name": "scheme",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationScheme"
+            },
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AuthenticateAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.AuthenticateResult>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ChallengeAsync",
+          "Parameters": [
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ForbidAsync",
+          "Parameters": [
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.IAuthenticationHandlerProvider",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "GetHandlerAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.IAuthenticationHandler>",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.IAuthenticationRequestHandler",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Authentication.IAuthenticationHandler"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "HandleRequestAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<System.Boolean>",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "GetAllSchemesAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Authentication.AuthenticationScheme>>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetSchemeAsync",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.AuthenticationScheme>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetDefaultAuthenticateSchemeAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.AuthenticationScheme>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetDefaultChallengeSchemeAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.AuthenticationScheme>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetDefaultForbidSchemeAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.AuthenticationScheme>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetDefaultSignInSchemeAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.AuthenticationScheme>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetDefaultSignOutSchemeAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.AuthenticationScheme>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AddScheme",
+          "Parameters": [
+            {
+              "Name": "scheme",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationScheme"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "RemoveScheme",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetRequestHandlerSchemesAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Authentication.AuthenticationScheme>>",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.IAuthenticationService",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "AuthenticateAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.AuthenticateResult>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ChallengeAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ForbidAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignInAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "principal",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignOutAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.IAuthenticationSignInHandler",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Authentication.IAuthenticationSignOutHandler"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "SignInAsync",
+          "Parameters": [
+            {
+              "Name": "user",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.IAuthenticationSignOutHandler",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Authentication.IAuthenticationHandler"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "SignOutAsync",
+          "Parameters": [
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.IClaimsTransformation",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "TransformAsync",
+          "Parameters": [
+            {
+              "Name": "principal",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Security.Claims.ClaimsPrincipal>",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.AuthenticationTokenExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "StoreTokens",
+          "Parameters": [
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            },
+            {
+              "Name": "tokens",
+              "Type": "System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Authentication.AuthenticationToken>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetTokenValue",
+          "Parameters": [
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            },
+            {
+              "Name": "tokenName",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UpdateTokenValue",
+          "Parameters": [
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            },
+            {
+              "Name": "tokenName",
+              "Type": "System.String"
+            },
+            {
+              "Name": "tokenValue",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetTokens",
+          "Parameters": [
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Authentication.AuthenticationToken>",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetTokenAsync",
+          "Parameters": [
+            {
+              "Name": "auth",
+              "Type": "Microsoft.AspNetCore.Authentication.IAuthenticationService"
+            },
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "tokenName",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.String>",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetTokenAsync",
+          "Parameters": [
+            {
+              "Name": "auth",
+              "Type": "Microsoft.AspNetCore.Authentication.IAuthenticationService"
+            },
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "tokenName",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.String>",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/Http/Authentication.Core/src/AuthenticationCoreServiceCollectionExtensions.cs b/src/Http/Authentication.Core/src/AuthenticationCoreServiceCollectionExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..fdf85a9b45691ae082e069703d9f3a05072326c3
--- /dev/null
+++ b/src/Http/Authentication.Core/src/AuthenticationCoreServiceCollectionExtensions.cs
@@ -0,0 +1,56 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Authentication;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+    /// <summary>
+    /// Extension methods for setting up authentication services in an <see cref="IServiceCollection" />.
+    /// </summary>
+    public static class AuthenticationCoreServiceCollectionExtensions
+    {
+        /// <summary>
+        /// Add core authentication services needed for <see cref="IAuthenticationService"/>.
+        /// </summary>
+        /// <param name="services">The <see cref="IServiceCollection"/>.</param>
+        /// <returns>The service collection.</returns>
+        public static IServiceCollection AddAuthenticationCore(this IServiceCollection services)
+        {
+            if (services == null)
+            {
+                throw new ArgumentNullException(nameof(services));
+            }
+
+            services.TryAddScoped<IAuthenticationService, AuthenticationService>();
+            services.TryAddSingleton<IClaimsTransformation, NoopClaimsTransformation>(); // Can be replaced with scoped ones that use DbContext
+            services.TryAddScoped<IAuthenticationHandlerProvider, AuthenticationHandlerProvider>();
+            services.TryAddSingleton<IAuthenticationSchemeProvider, AuthenticationSchemeProvider>();
+            return services;
+        }
+
+        /// <summary>
+        /// Add core authentication services needed for <see cref="IAuthenticationService"/>.
+        /// </summary>
+        /// <param name="services">The <see cref="IServiceCollection"/>.</param>
+        /// <param name="configureOptions">Used to configure the <see cref="AuthenticationOptions"/>.</param>
+        /// <returns>The service collection.</returns>
+        public static IServiceCollection AddAuthenticationCore(this IServiceCollection services, Action<AuthenticationOptions> configureOptions) {
+            if (services == null)
+            {
+                throw new ArgumentNullException(nameof(services));
+            }
+
+            if (configureOptions == null)
+            {
+                throw new ArgumentNullException(nameof(configureOptions));
+            }
+
+            services.AddAuthenticationCore();
+            services.Configure(configureOptions);
+            return services;
+        }
+    }
+}
diff --git a/src/Http/Authentication.Core/src/AuthenticationFeature.cs b/src/Http/Authentication.Core/src/AuthenticationFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3282cbf4671fbc396d0dc93dab9ce542afefcf72
--- /dev/null
+++ b/src/Http/Authentication.Core/src/AuthenticationFeature.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Used to capture path info so redirects can be computed properly within an app.Map().
+    /// </summary>
+    public class AuthenticationFeature : IAuthenticationFeature
+    {
+        /// <summary>
+        /// The original path base.
+        /// </summary>
+        public PathString OriginalPathBase { get; set; }
+
+        /// <summary>
+        /// The original path.
+        /// </summary>
+        public PathString OriginalPath { get; set; }
+    }
+}
diff --git a/src/Http/Authentication.Core/src/AuthenticationHandlerProvider.cs b/src/Http/Authentication.Core/src/AuthenticationHandlerProvider.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c4921e533491e1f25c97154631e44d6eb7d64367
--- /dev/null
+++ b/src/Http/Authentication.Core/src/AuthenticationHandlerProvider.cs
@@ -0,0 +1,63 @@
+// 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.Http;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Implementation of <see cref="IAuthenticationHandlerProvider"/>.
+    /// </summary>
+    public class AuthenticationHandlerProvider : IAuthenticationHandlerProvider
+    {
+        /// <summary>
+        /// Constructor.
+        /// </summary>
+        /// <param name="schemes">The <see cref="IAuthenticationHandlerProvider"/>.</param>
+        public AuthenticationHandlerProvider(IAuthenticationSchemeProvider schemes)
+        {
+            Schemes = schemes;
+        }
+
+        /// <summary>
+        /// The <see cref="IAuthenticationHandlerProvider"/>.
+        /// </summary>
+        public IAuthenticationSchemeProvider Schemes { get; }
+
+        // handler instance cache, need to initialize once per request
+        private Dictionary<string, IAuthenticationHandler> _handlerMap = new Dictionary<string, IAuthenticationHandler>(StringComparer.Ordinal);
+
+        /// <summary>
+        /// Returns the handler instance that will be used.
+        /// </summary>
+        /// <param name="context">The context.</param>
+        /// <param name="authenticationScheme">The name of the authentication scheme being handled.</param>
+        /// <returns>The handler instance.</returns>
+        public async Task<IAuthenticationHandler> GetHandlerAsync(HttpContext context, string authenticationScheme)
+        {
+            if (_handlerMap.ContainsKey(authenticationScheme))
+            {
+                return _handlerMap[authenticationScheme];
+            }
+
+            var scheme = await Schemes.GetSchemeAsync(authenticationScheme);
+            if (scheme == null)
+            {
+                return null;
+            }
+            var handler = (context.RequestServices.GetService(scheme.HandlerType) ??
+                ActivatorUtilities.CreateInstance(context.RequestServices, scheme.HandlerType))
+                as IAuthenticationHandler;
+            if (handler != null)
+            {
+                await handler.InitializeAsync(scheme, context);
+                _handlerMap[authenticationScheme] = handler;
+            }
+            return handler;
+        }
+    }
+}
diff --git a/src/Http/Authentication.Core/src/AuthenticationSchemeProvider.cs b/src/Http/Authentication.Core/src/AuthenticationSchemeProvider.cs
new file mode 100644
index 0000000000000000000000000000000000000000..050118d3c4386744a18f939dc54a649087162aa3
--- /dev/null
+++ b/src/Http/Authentication.Core/src/AuthenticationSchemeProvider.cs
@@ -0,0 +1,176 @@
+// 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.Http;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Implements <see cref="IAuthenticationSchemeProvider"/>.
+    /// </summary>
+    public class AuthenticationSchemeProvider : IAuthenticationSchemeProvider
+    {
+        /// <summary>
+        /// Creates an instance of <see cref="AuthenticationSchemeProvider"/>
+        /// using the specified <paramref name="options"/>,
+        /// </summary>
+        /// <param name="options">The <see cref="AuthenticationOptions"/> options.</param>
+        public AuthenticationSchemeProvider(IOptions<AuthenticationOptions> options)
+            : this(options, new Dictionary<string, AuthenticationScheme>(StringComparer.Ordinal))
+        {
+        }
+
+        /// <summary>
+        /// Creates an instance of <see cref="AuthenticationSchemeProvider"/>
+        /// using the specified <paramref name="options"/> and <paramref name="schemes"/>.
+        /// </summary>
+        /// <param name="options">The <see cref="AuthenticationOptions"/> options.</param>
+        /// <param name="schemes">The dictionary used to store authentication schemes.</param>
+        protected AuthenticationSchemeProvider(IOptions<AuthenticationOptions> options, IDictionary<string, AuthenticationScheme> schemes)
+        {
+            _options = options.Value;
+
+            _schemes = schemes ?? throw new ArgumentNullException(nameof(schemes));
+            _requestHandlers = new List<AuthenticationScheme>();
+
+            foreach (var builder in _options.Schemes)
+            {
+                var scheme = builder.Build();
+                AddScheme(scheme);
+            }
+        }
+
+        private readonly AuthenticationOptions _options;
+        private readonly object _lock = new object();
+
+        private readonly IDictionary<string, AuthenticationScheme> _schemes;
+        private readonly List<AuthenticationScheme> _requestHandlers;
+
+        private Task<AuthenticationScheme> GetDefaultSchemeAsync()
+            => _options.DefaultScheme != null
+            ? GetSchemeAsync(_options.DefaultScheme)
+            : Task.FromResult<AuthenticationScheme>(null);
+
+        /// <summary>
+        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.AuthenticateAsync(HttpContext, string)"/>.
+        /// This is typically specified via <see cref="AuthenticationOptions.DefaultAuthenticateScheme"/>.
+        /// Otherwise, this will fallback to <see cref="AuthenticationOptions.DefaultScheme"/>.
+        /// </summary>
+        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.AuthenticateAsync(HttpContext, string)"/>.</returns>
+        public virtual Task<AuthenticationScheme> GetDefaultAuthenticateSchemeAsync()
+            => _options.DefaultAuthenticateScheme != null
+            ? GetSchemeAsync(_options.DefaultAuthenticateScheme)
+            : GetDefaultSchemeAsync();
+
+        /// <summary>
+        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.ChallengeAsync(HttpContext, string, AuthenticationProperties)"/>.
+        /// This is typically specified via <see cref="AuthenticationOptions.DefaultChallengeScheme"/>.
+        /// Otherwise, this will fallback to <see cref="AuthenticationOptions.DefaultScheme"/>.
+        /// </summary>
+        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.ChallengeAsync(HttpContext, string, AuthenticationProperties)"/>.</returns>
+        public virtual Task<AuthenticationScheme> GetDefaultChallengeSchemeAsync()
+            => _options.DefaultChallengeScheme != null
+            ? GetSchemeAsync(_options.DefaultChallengeScheme)
+            : GetDefaultSchemeAsync();
+
+        /// <summary>
+        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.ForbidAsync(HttpContext, string, AuthenticationProperties)"/>.
+        /// This is typically specified via <see cref="AuthenticationOptions.DefaultForbidScheme"/>.
+        /// Otherwise, this will fallback to <see cref="GetDefaultChallengeSchemeAsync"/> .
+        /// </summary>
+        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.ForbidAsync(HttpContext, string, AuthenticationProperties)"/>.</returns>
+        public virtual Task<AuthenticationScheme> GetDefaultForbidSchemeAsync()
+            => _options.DefaultForbidScheme != null
+            ? GetSchemeAsync(_options.DefaultForbidScheme)
+            : GetDefaultChallengeSchemeAsync();
+
+        /// <summary>
+        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.SignInAsync(HttpContext, string, System.Security.Claims.ClaimsPrincipal, AuthenticationProperties)"/>.
+        /// This is typically specified via <see cref="AuthenticationOptions.DefaultSignInScheme"/>.
+        /// Otherwise, this will fallback to <see cref="AuthenticationOptions.DefaultScheme"/>.
+        /// </summary>
+        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.SignInAsync(HttpContext, string, System.Security.Claims.ClaimsPrincipal, AuthenticationProperties)"/>.</returns>
+        public virtual Task<AuthenticationScheme> GetDefaultSignInSchemeAsync()
+            => _options.DefaultSignInScheme != null
+            ? GetSchemeAsync(_options.DefaultSignInScheme)
+            : GetDefaultSchemeAsync();
+
+        /// <summary>
+        /// Returns the scheme that will be used by default for <see cref="IAuthenticationService.SignOutAsync(HttpContext, string, AuthenticationProperties)"/>.
+        /// This is typically specified via <see cref="AuthenticationOptions.DefaultSignOutScheme"/>.
+        /// Otherwise this will fallback to <see cref="GetDefaultSignInSchemeAsync"/> if that supoorts sign out.
+        /// </summary>
+        /// <returns>The scheme that will be used by default for <see cref="IAuthenticationService.SignOutAsync(HttpContext, string, AuthenticationProperties)"/>.</returns>
+        public virtual Task<AuthenticationScheme> GetDefaultSignOutSchemeAsync()
+            => _options.DefaultSignOutScheme != null
+            ? GetSchemeAsync(_options.DefaultSignOutScheme)
+            : GetDefaultSignInSchemeAsync();
+
+        /// <summary>
+        /// Returns the <see cref="AuthenticationScheme"/> matching the name, or null.
+        /// </summary>
+        /// <param name="name">The name of the authenticationScheme.</param>
+        /// <returns>The scheme or null if not found.</returns>
+        public virtual Task<AuthenticationScheme> GetSchemeAsync(string name)
+            => Task.FromResult(_schemes.ContainsKey(name) ? _schemes[name] : null);
+
+        /// <summary>
+        /// Returns the schemes in priority order for request handling.
+        /// </summary>
+        /// <returns>The schemes in priority order for request handling</returns>
+        public virtual Task<IEnumerable<AuthenticationScheme>> GetRequestHandlerSchemesAsync()
+            => Task.FromResult<IEnumerable<AuthenticationScheme>>(_requestHandlers);
+
+        /// <summary>
+        /// Registers a scheme for use by <see cref="IAuthenticationService"/>. 
+        /// </summary>
+        /// <param name="scheme">The scheme.</param>
+        public virtual void AddScheme(AuthenticationScheme scheme)
+        {
+            if (_schemes.ContainsKey(scheme.Name))
+            {
+                throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
+            }
+            lock (_lock)
+            {
+                if (_schemes.ContainsKey(scheme.Name))
+                {
+                    throw new InvalidOperationException("Scheme already exists: " + scheme.Name);
+                }
+                if (typeof(IAuthenticationRequestHandler).IsAssignableFrom(scheme.HandlerType))
+                {
+                    _requestHandlers.Add(scheme);
+                }
+                _schemes[scheme.Name] = scheme;
+            }
+        }
+
+        /// <summary>
+        /// Removes a scheme, preventing it from being used by <see cref="IAuthenticationService"/>.
+        /// </summary>
+        /// <param name="name">The name of the authenticationScheme being removed.</param>
+        public virtual void RemoveScheme(string name)
+        {
+            if (!_schemes.ContainsKey(name))
+            {
+                return;
+            }
+            lock (_lock)
+            {
+                if (_schemes.ContainsKey(name))
+                {
+                    var scheme = _schemes[name];
+                    _requestHandlers.Remove(scheme);
+                    _schemes.Remove(name);
+                }
+            }
+        }
+
+        public virtual Task<IEnumerable<AuthenticationScheme>> GetAllSchemesAsync()
+            => Task.FromResult<IEnumerable<AuthenticationScheme>>(_schemes.Values);
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Authentication.Core/src/AuthenticationService.cs b/src/Http/Authentication.Core/src/AuthenticationService.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3e46df2f24aa4c88cbb24a5f458e5e407df6fcd3
--- /dev/null
+++ b/src/Http/Authentication.Core/src/AuthenticationService.cs
@@ -0,0 +1,303 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Implements <see cref="IAuthenticationService"/>.
+    /// </summary>
+    public class AuthenticationService : IAuthenticationService
+    {
+        /// <summary>
+        /// Constructor.
+        /// </summary>
+        /// <param name="schemes">The <see cref="IAuthenticationSchemeProvider"/>.</param>
+        /// <param name="handlers">The <see cref="IAuthenticationRequestHandler"/>.</param>
+        /// <param name="transform">The <see cref="IClaimsTransformation"/>.</param>
+        public AuthenticationService(IAuthenticationSchemeProvider schemes, IAuthenticationHandlerProvider handlers, IClaimsTransformation transform)
+        {
+            Schemes = schemes;
+            Handlers = handlers;
+            Transform = transform;
+        }
+
+        /// <summary>
+        /// Used to lookup AuthenticationSchemes.
+        /// </summary>
+        public IAuthenticationSchemeProvider Schemes { get; }
+
+        /// <summary>
+        /// Used to resolve IAuthenticationHandler instances.
+        /// </summary>
+        public IAuthenticationHandlerProvider Handlers { get; }
+
+        /// <summary>
+        /// Used for claims transformation.
+        /// </summary>
+        public IClaimsTransformation Transform { get; }
+
+        /// <summary>
+        /// Authenticate for the specified authentication scheme.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/>.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <returns>The result.</returns>
+        public virtual async Task<AuthenticateResult> AuthenticateAsync(HttpContext context, string scheme)
+        {
+            if (scheme == null)
+            {
+                var defaultScheme = await Schemes.GetDefaultAuthenticateSchemeAsync();
+                scheme = defaultScheme?.Name;
+                if (scheme == null)
+                {
+                    throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultAuthenticateScheme found.");
+                }
+            }
+
+            var handler = await Handlers.GetHandlerAsync(context, scheme);
+            if (handler == null)
+            {
+                throw await CreateMissingHandlerException(scheme);
+            }
+
+            var result = await handler.AuthenticateAsync();
+            if (result != null && result.Succeeded)
+            {
+                var transformed = await Transform.TransformAsync(result.Principal);
+                return AuthenticateResult.Success(new AuthenticationTicket(transformed, result.Properties, result.Ticket.AuthenticationScheme));
+            }
+            return result;
+        }
+
+        /// <summary>
+        /// Challenge the specified authentication scheme.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/>.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/>.</param>
+        /// <returns>A task.</returns>
+        public virtual async Task ChallengeAsync(HttpContext context, string scheme, AuthenticationProperties properties)
+        {
+            if (scheme == null)
+            {
+                var defaultChallengeScheme = await Schemes.GetDefaultChallengeSchemeAsync();
+                scheme = defaultChallengeScheme?.Name;
+                if (scheme == null)
+                {
+                    throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultChallengeScheme found.");
+                }
+            }
+
+            var handler = await Handlers.GetHandlerAsync(context, scheme);
+            if (handler == null)
+            {
+                throw await CreateMissingHandlerException(scheme);
+            }
+
+            await handler.ChallengeAsync(properties);
+        }
+
+        /// <summary>
+        /// Forbid the specified authentication scheme.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/>.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/>.</param>
+        /// <returns>A task.</returns>
+        public virtual async Task ForbidAsync(HttpContext context, string scheme, AuthenticationProperties properties)
+        {
+            if (scheme == null)
+            {
+                var defaultForbidScheme = await Schemes.GetDefaultForbidSchemeAsync();
+                scheme = defaultForbidScheme?.Name;
+                if (scheme == null)
+                {
+                    throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultForbidScheme found.");
+                }
+            }
+
+            var handler = await Handlers.GetHandlerAsync(context, scheme);
+            if (handler == null)
+            {
+                throw await CreateMissingHandlerException(scheme);
+            }
+
+            await handler.ForbidAsync(properties);
+        }
+
+        /// <summary>
+        /// Sign a principal in for the specified authentication scheme.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/>.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <param name="principal">The <see cref="ClaimsPrincipal"/> to sign in.</param>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/>.</param>
+        /// <returns>A task.</returns>
+        public virtual async Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
+        {
+            if (principal == null)
+            {
+                throw new ArgumentNullException(nameof(principal));
+            }
+
+            if (scheme == null)
+            {
+                var defaultScheme = await Schemes.GetDefaultSignInSchemeAsync();
+                scheme = defaultScheme?.Name;
+                if (scheme == null)
+                {
+                    throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultSignInScheme found.");
+                }
+            }
+
+            var handler = await Handlers.GetHandlerAsync(context, scheme);
+            if (handler == null)
+            {
+                throw await CreateMissingSignInHandlerException(scheme);
+            }
+
+            var signInHandler = handler as IAuthenticationSignInHandler;
+            if (signInHandler == null)
+            {
+                throw await CreateMismatchedSignInHandlerException(scheme, handler);
+            }
+
+            await signInHandler.SignInAsync(principal, properties);
+        }
+
+        /// <summary>
+        /// Sign out the specified authentication scheme.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/>.</param>
+        /// <param name="scheme">The name of the authentication scheme.</param>
+        /// <param name="properties">The <see cref="AuthenticationProperties"/>.</param>
+        /// <returns>A task.</returns>
+        public virtual async Task SignOutAsync(HttpContext context, string scheme, AuthenticationProperties properties)
+        {
+            if (scheme == null)
+            {
+                var defaultScheme = await Schemes.GetDefaultSignOutSchemeAsync();
+                scheme = defaultScheme?.Name;
+                if (scheme == null)
+                {
+                    throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultSignOutScheme found.");
+                }
+            }
+
+            var handler = await Handlers.GetHandlerAsync(context, scheme);
+            if (handler == null)
+            {
+                throw await CreateMissingSignOutHandlerException(scheme);
+            }
+
+            var signOutHandler = handler as IAuthenticationSignOutHandler;
+            if (signOutHandler == null)
+            {
+                throw await CreateMismatchedSignOutHandlerException(scheme, handler);
+            }
+
+            await signOutHandler.SignOutAsync(properties);
+        }
+
+        private async Task<Exception> CreateMissingHandlerException(string scheme)
+        {
+            var schemes = string.Join(", ", (await Schemes.GetAllSchemesAsync()).Select(sch => sch.Name));
+
+            var footer = $" Did you forget to call AddAuthentication().Add[SomeAuthHandler](\"{scheme}\",...)?";
+
+            if (string.IsNullOrEmpty(schemes))
+            {
+                return new InvalidOperationException(
+                    $"No authentication handlers are registered." + footer);
+            }
+
+            return new InvalidOperationException(
+                $"No authentication handler is registered for the scheme '{scheme}'. The registered schemes are: {schemes}." + footer);
+        }
+
+        private async Task<string> GetAllSignInSchemeNames()
+        {
+            return string.Join(", ", (await Schemes.GetAllSchemesAsync())
+                .Where(sch => typeof(IAuthenticationSignInHandler).IsAssignableFrom(sch.HandlerType))
+                .Select(sch => sch.Name));
+        }
+
+        private async Task<Exception> CreateMissingSignInHandlerException(string scheme)
+        {
+            var schemes = await GetAllSignInSchemeNames();
+
+            // CookieAuth is the only implementation of sign-in.
+            var footer = $" Did you forget to call AddAuthentication().AddCookies(\"{scheme}\",...)?";
+
+            if (string.IsNullOrEmpty(schemes))
+            {
+                return new InvalidOperationException(
+                    $"No sign-in authentication handlers are registered." + footer);
+            }
+
+            return new InvalidOperationException(
+                $"No sign-in authentication handler is registered for the scheme '{scheme}'. The registered sign-in schemes are: {schemes}." + footer);
+        }
+
+        private async Task<Exception> CreateMismatchedSignInHandlerException(string scheme, IAuthenticationHandler handler)
+        {
+            var schemes = await GetAllSignInSchemeNames();
+
+            var mismatchError = $"The authentication handler registered for scheme '{scheme}' is '{handler.GetType().Name}' which cannot be used for SignInAsync. ";
+
+            if (string.IsNullOrEmpty(schemes))
+            {
+                // CookieAuth is the only implementation of sign-in.
+                return new InvalidOperationException(mismatchError
+                    + $"Did you forget to call AddAuthentication().AddCookies(\"Cookies\") and SignInAsync(\"Cookies\",...)?");
+            }
+
+            return new InvalidOperationException(mismatchError + $"The registered sign-in schemes are: {schemes}.");
+        }
+
+        private async Task<string> GetAllSignOutSchemeNames()
+        {
+            return string.Join(", ", (await Schemes.GetAllSchemesAsync())
+                .Where(sch => typeof(IAuthenticationSignOutHandler).IsAssignableFrom(sch.HandlerType))
+                .Select(sch => sch.Name));
+        }
+
+        private async Task<Exception> CreateMissingSignOutHandlerException(string scheme)
+        {
+            var schemes = await GetAllSignOutSchemeNames();
+
+            var footer = $" Did you forget to call AddAuthentication().AddCookies(\"{scheme}\",...)?";
+
+            if (string.IsNullOrEmpty(schemes))
+            {
+                // CookieAuth is the most common implementation of sign-out, but OpenIdConnect and WsFederation also support it.
+                return new InvalidOperationException($"No sign-out authentication handlers are registered." + footer);
+            }
+
+            return new InvalidOperationException(
+                $"No sign-out authentication handler is registered for the scheme '{scheme}'. The registered sign-out schemes are: {schemes}." + footer);
+        }
+
+        private async Task<Exception> CreateMismatchedSignOutHandlerException(string scheme, IAuthenticationHandler handler)
+        {
+            var schemes = await GetAllSignOutSchemeNames();
+
+            var mismatchError = $"The authentication handler registered for scheme '{scheme}' is '{handler.GetType().Name}' which cannot be used for {nameof(SignOutAsync)}. ";
+
+            if (string.IsNullOrEmpty(schemes))
+            {
+                // CookieAuth is the most common implementation of sign-out, but OpenIdConnect and WsFederation also support it.
+                return new InvalidOperationException(mismatchError
+                    + $"Did you forget to call AddAuthentication().AddCookies(\"Cookies\") and {nameof(SignOutAsync)}(\"Cookies\",...)?");
+            }
+
+            return new InvalidOperationException(mismatchError + $"The registered sign-out schemes are: {schemes}.");
+        }
+    }
+}
diff --git a/src/Http/Authentication.Core/src/Microsoft.AspNetCore.Authentication.Core.csproj b/src/Http/Authentication.Core/src/Microsoft.AspNetCore.Authentication.Core.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..c10bfb3656af825d39e106d6ab6678c7d9c4f14d
--- /dev/null
+++ b/src/Http/Authentication.Core/src/Microsoft.AspNetCore.Authentication.Core.csproj
@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>ASP.NET Core common types used by the various authentication middleware components.</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <NoWarn>$(NoWarn);CS1591</NoWarn>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore;authentication;security</PackageTags>
+    <EnableApiCheck>false</EnableApiCheck>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Authentication.Abstractions" />
+    <Reference Include="Microsoft.AspNetCore.Http" />
+    <Reference Include="Microsoft.AspNetCore.Http.Extensions" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/Authentication.Core/src/NoopClaimsTransformation.cs b/src/Http/Authentication.Core/src/NoopClaimsTransformation.cs
new file mode 100644
index 0000000000000000000000000000000000000000..83c488fe421b2fd73e85e1ab50f2d121507454f7
--- /dev/null
+++ b/src/Http/Authentication.Core/src/NoopClaimsTransformation.cs
@@ -0,0 +1,24 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    /// <summary>
+    /// Default claims transformation is a no-op.
+    /// </summary>
+    public class NoopClaimsTransformation : IClaimsTransformation
+    {
+        /// <summary>
+        /// Returns the principal unchanged.
+        /// </summary>
+        /// <param name="principal">The user.</param>
+        /// <returns>The principal unchanged.</returns>
+        public virtual Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
+        {
+            return Task.FromResult(principal);
+        }
+    }
+}
diff --git a/src/Http/Authentication.Core/src/baseline.netcore.json b/src/Http/Authentication.Core/src/baseline.netcore.json
new file mode 100644
index 0000000000000000000000000000000000000000..62aeb4473802c2a8fc4304687b439971f256c307
--- /dev/null
+++ b/src/Http/Authentication.Core/src/baseline.netcore.json
@@ -0,0 +1,515 @@
+{
+  "AssemblyIdentity": "Microsoft.AspNetCore.Authentication.Core, Version=2.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+  "Types": [
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.AuthenticationFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Authentication.IAuthenticationFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_OriginalPathBase",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_OriginalPathBase",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_OriginalPath",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_OriginalPath",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.AuthenticationHandlerProvider",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Authentication.IAuthenticationHandlerProvider"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Schemes",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHandlerAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.IAuthenticationHandler>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationHandlerProvider",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "schemes",
+              "Type": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.AuthenticationSchemeProvider",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "GetDefaultAuthenticateSchemeAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.AuthenticationScheme>",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetDefaultChallengeSchemeAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.AuthenticationScheme>",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetDefaultForbidSchemeAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.AuthenticationScheme>",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetDefaultSignInSchemeAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.AuthenticationScheme>",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetDefaultSignOutSchemeAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.AuthenticationScheme>",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetSchemeAsync",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.AuthenticationScheme>",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetRequestHandlerSchemesAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Authentication.AuthenticationScheme>>",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AddScheme",
+          "Parameters": [
+            {
+              "Name": "scheme",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationScheme"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "RemoveScheme",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetAllSchemesAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Authentication.AuthenticationScheme>>",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "options",
+              "Type": "Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Authentication.AuthenticationOptions>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.AuthenticationService",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Authentication.IAuthenticationService"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Schemes",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Handlers",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Authentication.IAuthenticationHandlerProvider",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Transform",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Authentication.IClaimsTransformation",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AuthenticateAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Authentication.AuthenticateResult>",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationService",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ChallengeAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationService",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ForbidAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationService",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignInAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "principal",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationService",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignOutAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IAuthenticationService",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "schemes",
+              "Type": "Microsoft.AspNetCore.Authentication.IAuthenticationSchemeProvider"
+            },
+            {
+              "Name": "handlers",
+              "Type": "Microsoft.AspNetCore.Authentication.IAuthenticationHandlerProvider"
+            },
+            {
+              "Name": "transform",
+              "Type": "Microsoft.AspNetCore.Authentication.IClaimsTransformation"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Authentication.NoopClaimsTransformation",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Authentication.IClaimsTransformation"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "TransformAsync",
+          "Parameters": [
+            {
+              "Name": "principal",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Security.Claims.ClaimsPrincipal>",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Authentication.IClaimsTransformation",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Extensions.DependencyInjection.AuthenticationCoreServiceCollectionExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "AddAuthenticationCore",
+          "Parameters": [
+            {
+              "Name": "services",
+              "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
+            }
+          ],
+          "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AddAuthenticationCore",
+          "Parameters": [
+            {
+              "Name": "services",
+              "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
+            },
+            {
+              "Name": "configureOptions",
+              "Type": "System.Action<Microsoft.AspNetCore.Authentication.AuthenticationOptions>"
+            }
+          ],
+          "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/Http/Authentication.Core/test/AuthenticationPropertiesTests.cs b/src/Http/Authentication.Core/test/AuthenticationPropertiesTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..639c9b558ea633576abc6b0db8b3b7d0c5b70112
--- /dev/null
+++ b/src/Http/Authentication.Core/test/AuthenticationPropertiesTests.cs
@@ -0,0 +1,207 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Authentication.Core.Test
+{
+    public class AuthenticationPropertiesTests
+    {
+        [Fact]
+        public void DefaultConstructor_EmptyCollections()
+        {
+            var props = new AuthenticationProperties();
+            Assert.Empty(props.Items);
+            Assert.Empty(props.Parameters);
+        }
+
+        [Fact]
+        public void ItemsConstructor_ReusesItemsDictionary()
+        {
+            var items = new Dictionary<string, string>
+            {
+                ["foo"] = "bar",
+            };
+            var props = new AuthenticationProperties(items);
+            Assert.Same(items, props.Items);
+            Assert.Empty(props.Parameters);
+        }
+
+        [Fact]
+        public void FullConstructor_ReusesDictionaries()
+        {
+            var items = new Dictionary<string, string>
+            {
+                ["foo"] = "bar",
+            };
+            var parameters = new Dictionary<string, object>
+            {
+                ["number"] = 1234,
+                ["list"] = new List<string> { "a", "b", "c" },
+            };
+            var props = new AuthenticationProperties(items, parameters);
+            Assert.Same(items, props.Items);
+            Assert.Same(parameters, props.Parameters);
+        }
+
+        [Fact]
+        public void GetSetString()
+        {
+            var props = new AuthenticationProperties();
+            Assert.Null(props.GetString("foo"));
+            Assert.Equal(0, props.Items.Count);
+
+            props.SetString("foo", "foo bar");
+            Assert.Equal("foo bar", props.GetString("foo"));
+            Assert.Equal("foo bar", props.Items["foo"]);
+            Assert.Equal(1, props.Items.Count);
+
+            props.SetString("foo", "foo baz");
+            Assert.Equal("foo baz", props.GetString("foo"));
+            Assert.Equal("foo baz", props.Items["foo"]);
+            Assert.Equal(1, props.Items.Count);
+
+            props.SetString("bar", "xy");
+            Assert.Equal("xy", props.GetString("bar"));
+            Assert.Equal("xy", props.Items["bar"]);
+            Assert.Equal(2, props.Items.Count);
+
+            props.SetString("bar", string.Empty);
+            Assert.Equal(string.Empty, props.GetString("bar"));
+            Assert.Equal(string.Empty, props.Items["bar"]);
+
+            props.SetString("foo", null);
+            Assert.Null(props.GetString("foo"));
+            Assert.Equal(1, props.Items.Count);
+        }
+
+        [Fact]
+        public void GetSetParameter_String()
+        {
+            var props = new AuthenticationProperties();
+            Assert.Null(props.GetParameter<string>("foo"));
+            Assert.Equal(0, props.Parameters.Count);
+
+            props.SetParameter<string>("foo", "foo bar");
+            Assert.Equal("foo bar", props.GetParameter<string>("foo"));
+            Assert.Equal("foo bar", props.Parameters["foo"]);
+            Assert.Equal(1, props.Parameters.Count);
+
+            props.SetParameter<string>("foo", null);
+            Assert.Null(props.GetParameter<string>("foo"));
+            Assert.Null(props.Parameters["foo"]);
+            Assert.Equal(1, props.Parameters.Count);
+        }
+
+        [Fact]
+        public void GetSetParameter_Int()
+        {
+            var props = new AuthenticationProperties();
+            Assert.Null(props.GetParameter<int?>("foo"));
+            Assert.Equal(0, props.Parameters.Count);
+
+            props.SetParameter<int?>("foo", 123);
+            Assert.Equal(123, props.GetParameter<int?>("foo"));
+            Assert.Equal(123, props.Parameters["foo"]);
+            Assert.Equal(1, props.Parameters.Count);
+
+            props.SetParameter<int?>("foo", null);
+            Assert.Null(props.GetParameter<int?>("foo"));
+            Assert.Null(props.Parameters["foo"]);
+            Assert.Equal(1, props.Parameters.Count);
+        }
+
+        [Fact]
+        public void GetSetParameter_Collection()
+        {
+            var props = new AuthenticationProperties();
+            Assert.Null(props.GetParameter<int?>("foo"));
+            Assert.Equal(0, props.Parameters.Count);
+
+            var list = new string[] { "a", "b", "c" };
+            props.SetParameter<ICollection<string>>("foo", list);
+            Assert.Equal(new string[] { "a", "b", "c" }, props.GetParameter<ICollection<string>>("foo"));
+            Assert.Same(list, props.Parameters["foo"]);
+            Assert.Equal(1, props.Parameters.Count);
+
+            props.SetParameter<ICollection<string>>("foo", null);
+            Assert.Null(props.GetParameter<ICollection<string>>("foo"));
+            Assert.Null(props.Parameters["foo"]);
+            Assert.Equal(1, props.Parameters.Count);
+        }
+
+        [Fact]
+        public void IsPersistent_Test()
+        {
+            var props = new AuthenticationProperties();
+            Assert.False(props.IsPersistent);
+
+            props.IsPersistent = true;
+            Assert.True(props.IsPersistent);
+            Assert.Equal(string.Empty, props.Items.First().Value);
+
+            props.Items.Clear();
+            Assert.False(props.IsPersistent);
+        }
+
+        [Fact]
+        public void RedirectUri_Test()
+        {
+            var props = new AuthenticationProperties();
+            Assert.Null(props.RedirectUri);
+
+            props.RedirectUri = "http://example.com";
+            Assert.Equal("http://example.com", props.RedirectUri);
+            Assert.Equal("http://example.com", props.Items.First().Value);
+
+            props.Items.Clear();
+            Assert.Null(props.RedirectUri);
+        }
+
+        [Fact]
+        public void IssuedUtc_Test()
+        {
+            var props = new AuthenticationProperties();
+            Assert.Null(props.IssuedUtc);
+
+            props.IssuedUtc = new DateTimeOffset(new DateTime(2018, 03, 21, 0, 0, 0, DateTimeKind.Utc));
+            Assert.Equal(new DateTimeOffset(new DateTime(2018, 03, 21, 0, 0, 0, DateTimeKind.Utc)), props.IssuedUtc);
+            Assert.Equal("Wed, 21 Mar 2018 00:00:00 GMT", props.Items.First().Value);
+
+            props.Items.Clear();
+            Assert.Null(props.IssuedUtc);
+        }
+
+        [Fact]
+        public void ExpiresUtc_Test()
+        {
+            var props = new AuthenticationProperties();
+            Assert.Null(props.ExpiresUtc);
+
+            props.ExpiresUtc = new DateTimeOffset(new DateTime(2018, 03, 19, 12, 34, 56, DateTimeKind.Utc));
+            Assert.Equal(new DateTimeOffset(new DateTime(2018, 03, 19, 12, 34, 56, DateTimeKind.Utc)), props.ExpiresUtc);
+            Assert.Equal("Mon, 19 Mar 2018 12:34:56 GMT", props.Items.First().Value);
+
+            props.Items.Clear();
+            Assert.Null(props.ExpiresUtc);
+        }
+
+        [Fact]
+        public void AllowRefresh_Test()
+        {
+            var props = new AuthenticationProperties();
+            Assert.Null(props.AllowRefresh);
+
+            props.AllowRefresh = true;
+            Assert.True(props.AllowRefresh);
+            Assert.Equal("True", props.Items.First().Value);
+
+            props.AllowRefresh = false;
+            Assert.False(props.AllowRefresh);
+            Assert.Equal("False", props.Items.First().Value);
+
+            props.Items.Clear();
+            Assert.Null(props.AllowRefresh);
+        }
+    }
+}
diff --git a/src/Http/Authentication.Core/test/AuthenticationSchemeProviderTests.cs b/src/Http/Authentication.Core/test/AuthenticationSchemeProviderTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..82602000aa3730cb592f7aa67af0e5c81f5d9585
--- /dev/null
+++ b/src/Http/Authentication.Core/test/AuthenticationSchemeProviderTests.cs
@@ -0,0 +1,207 @@
+
+// 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.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    public class AuthenticationSchemeProviderTests
+    {
+        [Fact]
+        public async Task NoDefaultsByDefault()
+        {
+            var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+            {
+                o.AddScheme<SignInHandler>("B", "whatever");
+            }).BuildServiceProvider();
+
+            var provider = services.GetRequiredService<IAuthenticationSchemeProvider>();
+            Assert.Null(await provider.GetDefaultForbidSchemeAsync());
+            Assert.Null(await provider.GetDefaultAuthenticateSchemeAsync());
+            Assert.Null(await provider.GetDefaultChallengeSchemeAsync());
+            Assert.Null(await provider.GetDefaultSignInSchemeAsync());
+            Assert.Null(await provider.GetDefaultSignOutSchemeAsync());
+        }
+
+        [Fact]
+        public async Task DefaultSchemesFallbackToDefaultScheme()
+        {
+            var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+            {
+                o.DefaultScheme = "B";
+                o.AddScheme<SignInHandler>("B", "whatever");
+            }).BuildServiceProvider();
+
+            var provider = services.GetRequiredService<IAuthenticationSchemeProvider>();
+            Assert.Equal("B", (await provider.GetDefaultForbidSchemeAsync()).Name);
+            Assert.Equal("B", (await provider.GetDefaultAuthenticateSchemeAsync()).Name);
+            Assert.Equal("B", (await provider.GetDefaultChallengeSchemeAsync()).Name);
+            Assert.Equal("B", (await provider.GetDefaultSignInSchemeAsync()).Name);
+            Assert.Equal("B", (await provider.GetDefaultSignOutSchemeAsync()).Name);
+        }
+
+
+        [Fact]
+        public async Task DefaultSignOutFallsbackToSignIn()
+        {
+            var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+            {
+                o.AddScheme<SignInHandler>("signin", "whatever");
+                o.AddScheme<Handler>("foobly", "whatever");
+                o.DefaultSignInScheme = "signin";
+            }).BuildServiceProvider();
+
+            var provider = services.GetRequiredService<IAuthenticationSchemeProvider>();
+            var scheme = await provider.GetDefaultSignOutSchemeAsync();
+            Assert.NotNull(scheme);
+            Assert.Equal("signin", scheme.Name);
+        }
+
+        [Fact]
+        public async Task DefaultForbidFallsbackToChallenge()
+        {
+            var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+            {
+                o.AddScheme<Handler>("challenge", "whatever");
+                o.AddScheme<Handler>("foobly", "whatever");
+                o.DefaultChallengeScheme = "challenge";
+            }).BuildServiceProvider();
+
+            var provider = services.GetRequiredService<IAuthenticationSchemeProvider>();
+            var scheme = await provider.GetDefaultForbidSchemeAsync();
+            Assert.NotNull(scheme);
+            Assert.Equal("challenge", scheme.Name);
+        }
+
+        [Fact]
+        public async Task DefaultSchemesAreSet()
+        {
+            var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+            {
+                o.AddScheme<SignInHandler>("A", "whatever");
+                o.AddScheme<SignInHandler>("B", "whatever");
+                o.AddScheme<SignInHandler>("C", "whatever");
+                o.AddScheme<SignInHandler>("Def", "whatever");
+                o.DefaultScheme = "Def";
+                o.DefaultChallengeScheme = "A";
+                o.DefaultForbidScheme = "B";
+                o.DefaultSignInScheme = "C";
+                o.DefaultSignOutScheme = "A";
+                o.DefaultAuthenticateScheme = "C";
+            }).BuildServiceProvider();
+
+            var provider = services.GetRequiredService<IAuthenticationSchemeProvider>();
+            Assert.Equal("B", (await provider.GetDefaultForbidSchemeAsync()).Name);
+            Assert.Equal("C", (await provider.GetDefaultAuthenticateSchemeAsync()).Name);
+            Assert.Equal("A", (await provider.GetDefaultChallengeSchemeAsync()).Name);
+            Assert.Equal("C", (await provider.GetDefaultSignInSchemeAsync()).Name);
+            Assert.Equal("A", (await provider.GetDefaultSignOutSchemeAsync()).Name);
+        }
+
+        [Fact]
+        public async Task SignOutWillDefaultsToSignInThatDoesNotSignOut()
+        {
+            var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+            {
+                o.AddScheme<Handler>("signin", "whatever");
+                o.DefaultSignInScheme = "signin";
+            }).BuildServiceProvider();
+
+            var provider = services.GetRequiredService<IAuthenticationSchemeProvider>();
+            Assert.NotNull(await provider.GetDefaultSignOutSchemeAsync());
+        }
+
+        [Fact]
+        public void SchemeRegistrationIsCaseSensitive()
+        {
+            var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+            {
+                o.AddScheme<Handler>("signin", "whatever");
+                o.AddScheme<Handler>("signin", "whatever");
+            }).BuildServiceProvider();
+
+            var error = Assert.Throws<InvalidOperationException>(() => services.GetRequiredService<IAuthenticationSchemeProvider>());
+
+            Assert.Contains("Scheme already exists: signin", error.Message);
+        }
+
+        [Fact]
+        public async Task LookupUsesProvidedStringComparer()
+        {
+            var services = new ServiceCollection().AddOptions()
+                .AddSingleton<IAuthenticationSchemeProvider, IgnoreCaseSchemeProvider>()
+                .AddAuthenticationCore(o => o.AddScheme<Handler>("signin", "whatever"))
+                .BuildServiceProvider();
+
+            var provider = services.GetRequiredService<IAuthenticationSchemeProvider>();
+
+            var a = await provider.GetSchemeAsync("signin");
+            var b = await provider.GetSchemeAsync("SignIn");
+            var c = await provider.GetSchemeAsync("SIGNIN");
+
+            Assert.NotNull(a);
+            Assert.Same(a, b);
+            Assert.Same(b, c);
+        }
+
+        private class Handler : IAuthenticationHandler
+        {
+            public Task<AuthenticateResult> AuthenticateAsync()
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task ChallengeAsync(AuthenticationProperties properties)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task ForbidAsync(AuthenticationProperties properties)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        private class SignInHandler : Handler, IAuthenticationSignInHandler
+        {
+            public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task SignOutAsync(AuthenticationProperties properties)
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        private class SignOutHandler : Handler, IAuthenticationSignOutHandler
+        {
+            public Task SignOutAsync(AuthenticationProperties properties)
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+        private class IgnoreCaseSchemeProvider : AuthenticationSchemeProvider
+        {
+            public IgnoreCaseSchemeProvider(IOptions<AuthenticationOptions> options)
+                : base(options, new Dictionary<string, AuthenticationScheme>(StringComparer.OrdinalIgnoreCase))
+            {
+            }
+        }
+    }
+}
diff --git a/src/Http/Authentication.Core/test/AuthenticationServiceTests.cs b/src/Http/Authentication.Core/test/AuthenticationServiceTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e21ea40d518ba720c756f46d3b9c798cfdc1983d
--- /dev/null
+++ b/src/Http/Authentication.Core/test/AuthenticationServiceTests.cs
@@ -0,0 +1,355 @@
+// 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.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    public class AuthenticationServiceTests
+    {
+        [Fact]
+        public async Task AuthenticateThrowsForSchemeMismatch()
+        {
+            var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+            {
+                o.AddScheme<BaseHandler>("base", "whatever");
+            }).BuildServiceProvider();
+            var context = new DefaultHttpContext();
+            context.RequestServices = services;
+
+            await context.AuthenticateAsync("base");
+            var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.AuthenticateAsync("missing"));
+            Assert.Contains("base", ex.Message);
+        }
+
+        [Fact]
+        public async Task ChallengeThrowsForSchemeMismatch()
+        {
+            var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+            {
+                o.AddScheme<BaseHandler>("base", "whatever");
+            }).BuildServiceProvider();
+            var context = new DefaultHttpContext();
+            context.RequestServices = services;
+
+            await context.ChallengeAsync("base");
+            var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.ChallengeAsync("missing"));
+            Assert.Contains("base", ex.Message);
+        }
+
+        [Fact]
+        public async Task ForbidThrowsForSchemeMismatch()
+        {
+            var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+            {
+                o.AddScheme<BaseHandler>("base", "whatever");
+            }).BuildServiceProvider();
+            var context = new DefaultHttpContext();
+            context.RequestServices = services;
+
+            await context.ForbidAsync("base");
+            var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.ForbidAsync("missing"));
+            Assert.Contains("base", ex.Message);
+        }
+
+        [Fact]
+        public async Task CanOnlySignInIfSupported()
+        {
+            var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+            {
+                o.AddScheme<UberHandler>("uber", "whatever");
+                o.AddScheme<BaseHandler>("base", "whatever");
+                o.AddScheme<SignInHandler>("signin", "whatever");
+                o.AddScheme<SignOutHandler>("signout", "whatever");
+            }).BuildServiceProvider();
+            var context = new DefaultHttpContext();
+            context.RequestServices = services;
+
+            await context.SignInAsync("uber", new ClaimsPrincipal(), null);
+            var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync("base", new ClaimsPrincipal(), null));
+            Assert.Contains("uber", ex.Message);
+            Assert.Contains("signin", ex.Message);
+            await context.SignInAsync("signin", new ClaimsPrincipal(), null);
+            ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync("signout", new ClaimsPrincipal(), null));
+            Assert.Contains("uber", ex.Message);
+            Assert.Contains("signin", ex.Message);
+        }
+
+        [Fact]
+        public async Task CanOnlySignOutIfSupported()
+        {
+            var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+            {
+                o.AddScheme<UberHandler>("uber", "whatever");
+                o.AddScheme<BaseHandler>("base", "whatever");
+                o.AddScheme<SignInHandler>("signin", "whatever");
+                o.AddScheme<SignOutHandler>("signout", "whatever");
+            }).BuildServiceProvider();
+            var context = new DefaultHttpContext();
+            context.RequestServices = services;
+
+            await context.SignOutAsync("uber");
+            var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync("base"));
+            Assert.Contains("uber", ex.Message);
+            Assert.Contains("signout", ex.Message);
+            await context.SignOutAsync("signout");
+            await context.SignOutAsync("signin");
+        }
+
+        [Fact]
+        public async Task ServicesWithDefaultIAuthenticationHandlerMethodsTest()
+        {
+            var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+            {
+                o.AddScheme<BaseHandler>("base", "whatever");
+                o.DefaultScheme = "base";
+            }).BuildServiceProvider();
+            var context = new DefaultHttpContext();
+            context.RequestServices = services;
+
+            await context.AuthenticateAsync();
+            await context.ChallengeAsync();
+            await context.ForbidAsync();
+            var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignOutAsync());
+            Assert.Contains("cannot be used for SignOutAsync", ex.Message);
+            ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
+            Assert.Contains("cannot be used for SignInAsync", ex.Message);
+        }
+
+        [Fact]
+        public async Task ServicesWithDefaultUberMethodsTest()
+        {
+            var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+            {
+                o.AddScheme<UberHandler>("base", "whatever");
+                o.DefaultScheme = "base";
+            }).BuildServiceProvider();
+            var context = new DefaultHttpContext();
+            context.RequestServices = services;
+
+            await context.AuthenticateAsync();
+            await context.ChallengeAsync();
+            await context.ForbidAsync();
+            await context.SignOutAsync();
+            await context.SignInAsync(new ClaimsPrincipal());
+        }
+
+        [Fact]
+        public async Task ServicesWithDefaultSignInMethodsTest()
+        {
+            var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+            {
+                o.AddScheme<SignInHandler>("base", "whatever");
+                o.DefaultScheme = "base";
+            }).BuildServiceProvider();
+            var context = new DefaultHttpContext();
+            context.RequestServices = services;
+
+            await context.AuthenticateAsync();
+            await context.ChallengeAsync();
+            await context.ForbidAsync();
+            await context.SignOutAsync();
+            await context.SignInAsync(new ClaimsPrincipal());
+        }
+
+        [Fact]
+        public async Task ServicesWithDefaultSignOutMethodsTest()
+        {
+            var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+            {
+                o.AddScheme<SignOutHandler>("base", "whatever");
+                o.DefaultScheme = "base";
+            }).BuildServiceProvider();
+            var context = new DefaultHttpContext();
+            context.RequestServices = services;
+
+            await context.AuthenticateAsync();
+            await context.ChallengeAsync();
+            await context.ForbidAsync();
+            await context.SignOutAsync();
+            var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.SignInAsync(new ClaimsPrincipal()));
+            Assert.Contains("cannot be used for SignInAsync", ex.Message);
+        }
+
+        [Fact]
+        public async Task ServicesWithDefaultForbidMethod_CallsForbidMethod()
+        {
+            var services = new ServiceCollection().AddOptions().AddAuthenticationCore(o =>
+            {
+                o.AddScheme<ForbidHandler>("forbid", "whatever");
+                o.DefaultForbidScheme = "forbid";
+            }).BuildServiceProvider();
+            var context = new DefaultHttpContext();
+            context.RequestServices = services;
+
+            await context.ForbidAsync();
+        }
+
+
+        private class BaseHandler : IAuthenticationHandler
+        {
+            public Task<AuthenticateResult> AuthenticateAsync()
+            {
+                return Task.FromResult(AuthenticateResult.NoResult());
+            }
+
+            public Task ChallengeAsync(AuthenticationProperties properties)
+            {
+                return Task.FromResult(0);
+            }
+
+            public Task ForbidAsync(AuthenticationProperties properties)
+            {
+                return Task.FromResult(0);
+            }
+
+            public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+            {
+                return Task.FromResult(0);
+            }
+        }
+
+        private class SignInHandler : IAuthenticationSignInHandler
+        {
+            public Task<AuthenticateResult> AuthenticateAsync()
+            {
+                return Task.FromResult(AuthenticateResult.NoResult());
+            }
+
+            public Task ChallengeAsync(AuthenticationProperties properties)
+            {
+                return Task.FromResult(0);
+            }
+
+            public Task ForbidAsync(AuthenticationProperties properties)
+            {
+                return Task.FromResult(0);
+            }
+
+            public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+            {
+                return Task.FromResult(0);
+            }
+
+            public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
+            {
+                return Task.FromResult(0);
+            }
+
+            public Task SignOutAsync(AuthenticationProperties properties)
+            {
+                return Task.FromResult(0);
+            }
+        }
+
+        public class SignOutHandler : IAuthenticationSignOutHandler
+        {
+            public Task<AuthenticateResult> AuthenticateAsync()
+            {
+                return Task.FromResult(AuthenticateResult.NoResult());
+            }
+
+            public Task ChallengeAsync(AuthenticationProperties properties)
+            {
+                return Task.FromResult(0);
+            }
+
+            public Task ForbidAsync(AuthenticationProperties properties)
+            {
+                return Task.FromResult(0);
+            }
+
+            public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+            {
+                return Task.FromResult(0);
+            }
+
+            public Task SignOutAsync(AuthenticationProperties properties)
+            {
+                return Task.FromResult(0);
+            }
+        }
+
+        private class UberHandler : IAuthenticationHandler, IAuthenticationRequestHandler, IAuthenticationSignInHandler, IAuthenticationSignOutHandler
+        {
+            public Task<AuthenticateResult> AuthenticateAsync()
+            {
+                return Task.FromResult(AuthenticateResult.NoResult());
+            }
+
+            public Task ChallengeAsync(AuthenticationProperties properties)
+            {
+                return Task.FromResult(0);
+            }
+
+            public Task ForbidAsync(AuthenticationProperties properties)
+            {
+                return Task.FromResult(0);
+            }
+
+            public Task<bool> HandleRequestAsync()
+            {
+                return Task.FromResult(false);
+            }
+
+            public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+            {
+                return Task.FromResult(0);
+            }
+
+            public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
+            {
+                return Task.FromResult(0);
+            }
+
+            public Task SignOutAsync(AuthenticationProperties properties)
+            {
+                return Task.FromResult(0);
+            }
+        }
+
+        private class ForbidHandler : IAuthenticationHandler, IAuthenticationRequestHandler, IAuthenticationSignInHandler, IAuthenticationSignOutHandler
+        {
+            public Task<AuthenticateResult> AuthenticateAsync()
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task ChallengeAsync(AuthenticationProperties properties)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task ForbidAsync(AuthenticationProperties properties)
+            {
+                return Task.FromResult(0);
+            }
+
+            public Task<bool> HandleRequestAsync()
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+            {
+                return Task.FromResult(0);
+            }
+
+            public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task SignOutAsync(AuthenticationProperties properties)
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+    }
+}
diff --git a/src/Http/Authentication.Core/test/Microsoft.AspNetCore.Authentication.Core.Test.csproj b/src/Http/Authentication.Core/test/Microsoft.AspNetCore.Authentication.Core.Test.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..48197031971f8ef6cd94fdad9e41bf4392c79f6e
--- /dev/null
+++ b/src/Http/Authentication.Core/test/Microsoft.AspNetCore.Authentication.Core.Test.csproj
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Authentication.Core" />
+    <Reference Include="Microsoft.Extensions.DependencyInjection" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/Authentication.Core/test/TokenExtensionTests.cs b/src/Http/Authentication.Core/test/TokenExtensionTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7215d526e9dbfa1067c43f540094624c6dd3c32a
--- /dev/null
+++ b/src/Http/Authentication.Core/test/TokenExtensionTests.cs
@@ -0,0 +1,200 @@
+// 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.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Authentication
+{
+    public class TokenExtensionTests
+    {
+        [Fact]
+        public void CanStoreMultipleTokens()
+        {
+            var props = new AuthenticationProperties();
+            var tokens = new List<AuthenticationToken>();
+            var tok1 = new AuthenticationToken { Name = "One", Value = "1" };
+            var tok2 = new AuthenticationToken { Name = "Two", Value = "2" };
+            var tok3 = new AuthenticationToken { Name = "Three", Value = "3" };
+            tokens.Add(tok1);
+            tokens.Add(tok2);
+            tokens.Add(tok3);
+            props.StoreTokens(tokens);
+
+            Assert.Equal("1", props.GetTokenValue("One"));
+            Assert.Equal("2", props.GetTokenValue("Two"));
+            Assert.Equal("3", props.GetTokenValue("Three"));
+            Assert.Equal(3, props.GetTokens().Count());
+        }
+
+        [Fact]
+        public void SubsequentStoreTokenDeletesPreviousTokens()
+        {
+            var props = new AuthenticationProperties();
+            var tokens = new List<AuthenticationToken>();
+            var tok1 = new AuthenticationToken { Name = "One", Value = "1" };
+            var tok2 = new AuthenticationToken { Name = "Two", Value = "2" };
+            var tok3 = new AuthenticationToken { Name = "Three", Value = "3" };
+            tokens.Add(tok1);
+            tokens.Add(tok2);
+            tokens.Add(tok3);
+
+            props.StoreTokens(tokens);
+
+            props.StoreTokens(new[] { new AuthenticationToken { Name = "Zero", Value = "0" } });
+
+            Assert.Equal("0", props.GetTokenValue("Zero"));
+            Assert.Null(props.GetTokenValue("One"));
+            Assert.Null(props.GetTokenValue("Two"));
+            Assert.Null(props.GetTokenValue("Three"));
+            Assert.Single(props.GetTokens());
+        }
+
+        [Fact]
+        public void CanUpdateTokens()
+        {
+            var props = new AuthenticationProperties();
+            var tokens = new List<AuthenticationToken>();
+            var tok1 = new AuthenticationToken { Name = "One", Value = "1" };
+            var tok2 = new AuthenticationToken { Name = "Two", Value = "2" };
+            var tok3 = new AuthenticationToken { Name = "Three", Value = "3" };
+            tokens.Add(tok1);
+            tokens.Add(tok2);
+            tokens.Add(tok3);
+            props.StoreTokens(tokens);
+
+            tok1.Value = ".1";
+            tok2.Value = ".2";
+            tok3.Value = ".3";
+            props.StoreTokens(tokens);
+
+            Assert.Equal(".1", props.GetTokenValue("One"));
+            Assert.Equal(".2", props.GetTokenValue("Two"));
+            Assert.Equal(".3", props.GetTokenValue("Three"));
+            Assert.Equal(3, props.GetTokens().Count());
+        }
+
+        [Fact]
+        public void CanUpdateTokenValues()
+        {
+            var props = new AuthenticationProperties();
+            var tokens = new List<AuthenticationToken>();
+            var tok1 = new AuthenticationToken { Name = "One", Value = "1" };
+            var tok2 = new AuthenticationToken { Name = "Two", Value = "2" };
+            var tok3 = new AuthenticationToken { Name = "Three", Value = "3" };
+            tokens.Add(tok1);
+            tokens.Add(tok2);
+            tokens.Add(tok3);
+            props.StoreTokens(tokens);
+
+            Assert.True(props.UpdateTokenValue("One", ".11"));
+            Assert.True(props.UpdateTokenValue("Two", ".22"));
+            Assert.True(props.UpdateTokenValue("Three", ".33"));
+
+            Assert.Equal(".11", props.GetTokenValue("One"));
+            Assert.Equal(".22", props.GetTokenValue("Two"));
+            Assert.Equal(".33", props.GetTokenValue("Three"));
+            Assert.Equal(3, props.GetTokens().Count());
+        }
+
+        [Fact]
+        public void UpdateTokenValueReturnsFalseForUnknownToken()
+        {
+            var props = new AuthenticationProperties();
+            var tokens = new List<AuthenticationToken>();
+            var tok1 = new AuthenticationToken { Name = "One", Value = "1" };
+            var tok2 = new AuthenticationToken { Name = "Two", Value = "2" };
+            var tok3 = new AuthenticationToken { Name = "Three", Value = "3" };
+            tokens.Add(tok1);
+            tokens.Add(tok2);
+            tokens.Add(tok3);
+            props.StoreTokens(tokens);
+
+            Assert.False(props.UpdateTokenValue("ONE", ".11"));
+            Assert.False(props.UpdateTokenValue("Jigglypuff", ".11"));
+
+            Assert.Null(props.GetTokenValue("ONE"));
+            Assert.Null(props.GetTokenValue("Jigglypuff"));
+            Assert.Equal(3, props.GetTokens().Count());
+        }
+
+        [Fact]
+        public async Task GetTokenWorksWithDefaultAuthenticateScheme()
+        {
+            var context = new DefaultHttpContext();
+            var services = new ServiceCollection().AddOptions()
+                .AddAuthenticationCore(o =>
+                {
+                    o.DefaultScheme = "simple";
+                    o.AddScheme("simple", s => s.HandlerType = typeof(SimpleAuth));
+                });
+            context.RequestServices = services.BuildServiceProvider();
+
+            Assert.Equal("1", await context.GetTokenAsync("One"));
+            Assert.Equal("2", await context.GetTokenAsync("Two"));
+            Assert.Equal("3", await context.GetTokenAsync("Three"));
+        }
+
+        [Fact]
+        public async Task GetTokenWorksWithExplicitScheme()
+        {
+            var context = new DefaultHttpContext();
+            var services = new ServiceCollection().AddOptions()
+                .AddAuthenticationCore(o => o.AddScheme("simple", s => s.HandlerType = typeof(SimpleAuth)));
+            context.RequestServices = services.BuildServiceProvider();
+
+            Assert.Equal("1", await context.GetTokenAsync("simple", "One"));
+            Assert.Equal("2", await context.GetTokenAsync("simple", "Two"));
+            Assert.Equal("3", await context.GetTokenAsync("simple", "Three"));
+        }
+
+        private class SimpleAuth : IAuthenticationHandler
+        {
+            public Task<AuthenticateResult> AuthenticateAsync()
+            {
+                var props = new AuthenticationProperties();
+                var tokens = new List<AuthenticationToken>();
+                var tok1 = new AuthenticationToken { Name = "One", Value = "1" };
+                var tok2 = new AuthenticationToken { Name = "Two", Value = "2" };
+                var tok3 = new AuthenticationToken { Name = "Three", Value = "3" };
+                tokens.Add(tok1);
+                tokens.Add(tok2);
+                tokens.Add(tok3);
+                props.StoreTokens(tokens);
+                return Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(), props, "simple")));
+            }
+
+            public Task ChallengeAsync(AuthenticationProperties properties)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task ForbidAsync(AuthenticationProperties properties)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
+            {
+                return Task.FromResult(0);
+            }
+
+            public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task SignOutAsync(AuthenticationProperties properties)
+            {
+                throw new NotImplementedException();
+            }
+        }
+
+    }
+}
diff --git a/src/Http/Headers/src/BaseHeaderParser.cs b/src/Http/Headers/src/BaseHeaderParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f3caaafb706cd198897903771eeebaceeca3091a
--- /dev/null
+++ b/src/Http/Headers/src/BaseHeaderParser.cs
@@ -0,0 +1,72 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    internal abstract class BaseHeaderParser<T> : HttpHeaderParser<T>
+    {
+        protected BaseHeaderParser(bool supportsMultipleValues)
+            : base(supportsMultipleValues)
+        {
+        }
+
+        protected abstract int GetParsedValueLength(StringSegment value, int startIndex, out T parsedValue);
+
+        public sealed override bool TryParseValue(StringSegment value, ref int index, out T parsedValue)
+        {
+            parsedValue = default(T);
+
+            // If multiple values are supported (i.e. list of values), then accept an empty string: The header may
+            // be added multiple times to the request/response message. E.g.
+            //  Accept: text/xml; q=1
+            //  Accept:
+            //  Accept: text/plain; q=0.2
+            if (StringSegment.IsNullOrEmpty(value) || (index == value.Length))
+            {
+                return SupportsMultipleValues;
+            }
+
+            var separatorFound = false;
+            var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, index, SupportsMultipleValues,
+                out separatorFound);
+
+            if (separatorFound && !SupportsMultipleValues)
+            {
+                return false; // leading separators not allowed if we don't support multiple values.
+            }
+
+            if (current == value.Length)
+            {
+                if (SupportsMultipleValues)
+                {
+                    index = current;
+                }
+                return SupportsMultipleValues;
+            }
+
+            T result;
+            var length = GetParsedValueLength(value, current, out result);
+
+            if (length == 0)
+            {
+                return false;
+            }
+
+            current = current + length;
+            current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(value, current, SupportsMultipleValues,
+                out separatorFound);
+
+            // If we support multiple values and we've not reached the end of the string, then we must have a separator.
+            if ((separatorFound && !SupportsMultipleValues) || (!separatorFound && (current < value.Length)))
+            {
+                return false;
+            }
+
+            index = current;
+            parsedValue = result;
+            return true;
+        }
+    }
+}
diff --git a/src/Http/Headers/src/CacheControlHeaderValue.cs b/src/Http/Headers/src/CacheControlHeaderValue.cs
new file mode 100644
index 0000000000000000000000000000000000000000..81e18faf473efad6182d104c57c95232f733afa8
--- /dev/null
+++ b/src/Http/Headers/src/CacheControlHeaderValue.cs
@@ -0,0 +1,664 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Globalization;
+using System.Text;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class CacheControlHeaderValue
+    {
+        public static readonly string PublicString = "public";
+        public static readonly string PrivateString = "private";
+        public static readonly string MaxAgeString = "max-age";
+        public static readonly string SharedMaxAgeString = "s-maxage";
+        public static readonly string NoCacheString = "no-cache";
+        public static readonly string NoStoreString = "no-store";
+        public static readonly string MaxStaleString = "max-stale";
+        public static readonly string MinFreshString = "min-fresh";
+        public static readonly string NoTransformString = "no-transform";
+        public static readonly string OnlyIfCachedString = "only-if-cached";
+        public static readonly string MustRevalidateString = "must-revalidate";
+        public static readonly string ProxyRevalidateString = "proxy-revalidate";
+
+        // The Cache-Control header is special: It is a header supporting a list of values, but we represent the list
+        // as _one_ instance of CacheControlHeaderValue. I.e we set 'SupportsMultipleValues' to 'true' since it is
+        // OK to have multiple Cache-Control headers in a request/response message. However, after parsing all
+        // Cache-Control headers, only one instance of CacheControlHeaderValue is created (if all headers contain valid
+        // values, otherwise we may have multiple strings containing the invalid values).
+        private static readonly HttpHeaderParser<CacheControlHeaderValue> Parser
+            = new GenericHeaderParser<CacheControlHeaderValue>(true, GetCacheControlLength);
+
+        private static readonly Action<StringSegment> CheckIsValidTokenAction = CheckIsValidToken;
+
+        private bool _noCache;
+        private ICollection<StringSegment> _noCacheHeaders;
+        private bool _noStore;
+        private TimeSpan? _maxAge;
+        private TimeSpan? _sharedMaxAge;
+        private bool _maxStale;
+        private TimeSpan? _maxStaleLimit;
+        private TimeSpan? _minFresh;
+        private bool _noTransform;
+        private bool _onlyIfCached;
+        private bool _public;
+        private bool _private;
+        private ICollection<StringSegment> _privateHeaders;
+        private bool _mustRevalidate;
+        private bool _proxyRevalidate;
+        private IList<NameValueHeaderValue> _extensions;
+
+        public CacheControlHeaderValue()
+        {
+            // This type is unique in that there is no single required parameter.
+        }
+
+        public bool NoCache
+        {
+            get { return _noCache; }
+            set { _noCache = value; }
+        }
+
+        public ICollection<StringSegment> NoCacheHeaders
+        {
+            get
+            {
+                if (_noCacheHeaders == null)
+                {
+                    _noCacheHeaders = new ObjectCollection<StringSegment>(CheckIsValidTokenAction);
+                }
+                return _noCacheHeaders;
+            }
+        }
+
+        public bool NoStore
+        {
+            get { return _noStore; }
+            set { _noStore = value; }
+        }
+
+        public TimeSpan? MaxAge
+        {
+            get { return _maxAge; }
+            set { _maxAge = value; }
+        }
+
+        public TimeSpan? SharedMaxAge
+        {
+            get { return _sharedMaxAge; }
+            set { _sharedMaxAge = value; }
+        }
+
+        public bool MaxStale
+        {
+            get { return _maxStale; }
+            set { _maxStale = value; }
+        }
+
+        public TimeSpan? MaxStaleLimit
+        {
+            get { return _maxStaleLimit; }
+            set { _maxStaleLimit = value; }
+        }
+
+        public TimeSpan? MinFresh
+        {
+            get { return _minFresh; }
+            set { _minFresh = value; }
+        }
+
+        public bool NoTransform
+        {
+            get { return _noTransform; }
+            set { _noTransform = value; }
+        }
+
+        public bool OnlyIfCached
+        {
+            get { return _onlyIfCached; }
+            set { _onlyIfCached = value; }
+        }
+
+        public bool Public
+        {
+            get { return _public; }
+            set { _public = value; }
+        }
+
+        public bool Private
+        {
+            get { return _private; }
+            set { _private = value; }
+        }
+
+        public ICollection<StringSegment> PrivateHeaders
+        {
+            get
+            {
+                if (_privateHeaders == null)
+                {
+                    _privateHeaders = new ObjectCollection<StringSegment>(CheckIsValidTokenAction);
+                }
+                return _privateHeaders;
+            }
+        }
+
+        public bool MustRevalidate
+        {
+            get { return _mustRevalidate; }
+            set { _mustRevalidate = value; }
+        }
+
+        public bool ProxyRevalidate
+        {
+            get { return _proxyRevalidate; }
+            set { _proxyRevalidate = value; }
+        }
+
+        public IList<NameValueHeaderValue> Extensions
+        {
+            get
+            {
+                if (_extensions == null)
+                {
+                    _extensions = new ObjectCollection<NameValueHeaderValue>();
+                }
+                return _extensions;
+            }
+        }
+
+        public override string ToString()
+        {
+            var sb = new StringBuilder();
+
+            AppendValueIfRequired(sb, _noStore, NoStoreString);
+            AppendValueIfRequired(sb, _noTransform, NoTransformString);
+            AppendValueIfRequired(sb, _onlyIfCached, OnlyIfCachedString);
+            AppendValueIfRequired(sb, _public, PublicString);
+            AppendValueIfRequired(sb, _mustRevalidate, MustRevalidateString);
+            AppendValueIfRequired(sb, _proxyRevalidate, ProxyRevalidateString);
+
+            if (_noCache)
+            {
+                AppendValueWithSeparatorIfRequired(sb, NoCacheString);
+                if ((_noCacheHeaders != null) && (_noCacheHeaders.Count > 0))
+                {
+                    sb.Append("=\"");
+                    AppendValues(sb, _noCacheHeaders);
+                    sb.Append('\"');
+                }
+            }
+
+            if (_maxAge.HasValue)
+            {
+                AppendValueWithSeparatorIfRequired(sb, MaxAgeString);
+                sb.Append('=');
+                sb.Append(((int)_maxAge.Value.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
+            }
+
+            if (_sharedMaxAge.HasValue)
+            {
+                AppendValueWithSeparatorIfRequired(sb, SharedMaxAgeString);
+                sb.Append('=');
+                sb.Append(((int)_sharedMaxAge.Value.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
+            }
+
+            if (_maxStale)
+            {
+                AppendValueWithSeparatorIfRequired(sb, MaxStaleString);
+                if (_maxStaleLimit.HasValue)
+                {
+                    sb.Append('=');
+                    sb.Append(((int)_maxStaleLimit.Value.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
+                }
+            }
+
+            if (_minFresh.HasValue)
+            {
+                AppendValueWithSeparatorIfRequired(sb, MinFreshString);
+                sb.Append('=');
+                sb.Append(((int)_minFresh.Value.TotalSeconds).ToString(NumberFormatInfo.InvariantInfo));
+            }
+
+            if (_private)
+            {
+                AppendValueWithSeparatorIfRequired(sb, PrivateString);
+                if ((_privateHeaders != null) && (_privateHeaders.Count > 0))
+                {
+                    sb.Append("=\"");
+                    AppendValues(sb, _privateHeaders);
+                    sb.Append('\"');
+                }
+            }
+
+            NameValueHeaderValue.ToString(_extensions, ',', false, sb);
+
+            return sb.ToString();
+        }
+
+        public override bool Equals(object obj)
+        {
+            var other = obj as CacheControlHeaderValue;
+
+            if (other == null)
+            {
+                return false;
+            }
+
+            if ((_noCache != other._noCache) || (_noStore != other._noStore) || (_maxAge != other._maxAge) ||
+                (_sharedMaxAge != other._sharedMaxAge) || (_maxStale != other._maxStale) ||
+                (_maxStaleLimit != other._maxStaleLimit) || (_minFresh != other._minFresh) ||
+                (_noTransform != other._noTransform) || (_onlyIfCached != other._onlyIfCached) ||
+                (_public != other._public) || (_private != other._private) ||
+                (_mustRevalidate != other._mustRevalidate) || (_proxyRevalidate != other._proxyRevalidate))
+            {
+                return false;
+            }
+
+            if (!HeaderUtilities.AreEqualCollections(_noCacheHeaders, other._noCacheHeaders,
+                StringSegmentComparer.OrdinalIgnoreCase))
+            {
+                return false;
+            }
+
+            if (!HeaderUtilities.AreEqualCollections(_privateHeaders, other._privateHeaders,
+                StringSegmentComparer.OrdinalIgnoreCase))
+            {
+                return false;
+            }
+
+            if (!HeaderUtilities.AreEqualCollections(_extensions, other._extensions))
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        public override int GetHashCode()
+        {
+            // Use a different bit for bool fields: bool.GetHashCode() will return 0 (false) or 1 (true). So we would
+            // end up having the same hash code for e.g. two instances where one has only noCache set and the other
+            // only noStore.
+            int result = _noCache.GetHashCode() ^ (_noStore.GetHashCode() << 1) ^ (_maxStale.GetHashCode() << 2) ^
+                (_noTransform.GetHashCode() << 3) ^ (_onlyIfCached.GetHashCode() << 4) ^
+                (_public.GetHashCode() << 5) ^ (_private.GetHashCode() << 6) ^
+                (_mustRevalidate.GetHashCode() << 7) ^ (_proxyRevalidate.GetHashCode() << 8);
+
+            // XOR the hashcode of timespan values with different numbers to make sure two instances with the same
+            // timespan set on different fields result in different hashcodes.
+            result = result ^ (_maxAge.HasValue ? _maxAge.Value.GetHashCode() ^ 1 : 0) ^
+                (_sharedMaxAge.HasValue ? _sharedMaxAge.Value.GetHashCode() ^ 2 : 0) ^
+                (_maxStaleLimit.HasValue ? _maxStaleLimit.Value.GetHashCode() ^ 4 : 0) ^
+                (_minFresh.HasValue ? _minFresh.Value.GetHashCode() ^ 8 : 0);
+
+            if ((_noCacheHeaders != null) && (_noCacheHeaders.Count > 0))
+            {
+                foreach (var noCacheHeader in _noCacheHeaders)
+                {
+                    result = result ^ StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(noCacheHeader);
+                }
+            }
+
+            if ((_privateHeaders != null) && (_privateHeaders.Count > 0))
+            {
+                foreach (var privateHeader in _privateHeaders)
+                {
+                    result = result ^ StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(privateHeader);
+                }
+            }
+
+            if ((_extensions != null) && (_extensions.Count > 0))
+            {
+                foreach (var extension in _extensions)
+                {
+                    result = result ^ extension.GetHashCode();
+                }
+            }
+
+            return result;
+        }
+
+        public static CacheControlHeaderValue Parse(StringSegment input)
+        {
+            int index = 0;
+            // Cache-Control is unusual because there are no required values so the parser will succeed for an empty string, but still return null.
+            var result = Parser.ParseValue(input, ref index);
+            if (result == null)
+            {
+                throw new FormatException("No cache directives found.");
+            }
+            return result;
+        }
+
+        public static bool TryParse(StringSegment input, out CacheControlHeaderValue parsedValue)
+        {
+            int index = 0;
+            // Cache-Control is unusual because there are no required values so the parser will succeed for an empty string, but still return null.
+            if (Parser.TryParseValue(input, ref index, out parsedValue) && parsedValue != null)
+            {
+                return true;
+            }
+            parsedValue = null;
+            return false;
+        }
+
+        private static int GetCacheControlLength(StringSegment input, int startIndex, out CacheControlHeaderValue parsedValue)
+        {
+            Contract.Requires(startIndex >= 0);
+
+            parsedValue = null;
+
+            if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+            {
+                return 0;
+            }
+
+            // Cache-Control header consists of a list of name/value pairs, where the value is optional. So use an
+            // instance of NameValueHeaderParser to parse the string.
+            var current = startIndex;
+            NameValueHeaderValue nameValue = null;
+            var nameValueList = new List<NameValueHeaderValue>();
+            while (current < input.Length)
+            {
+                if (!NameValueHeaderValue.MultipleValueParser.TryParseValue(input, ref current, out nameValue))
+                {
+                    return 0;
+                }
+
+                nameValueList.Add(nameValue);
+            }
+
+            // If we get here, we were able to successfully parse the string as list of name/value pairs. Now analyze
+            // the name/value pairs.
+
+            // Cache-Control is a header supporting lists of values. However, expose the header as an instance of
+            // CacheControlHeaderValue.
+            var result = new CacheControlHeaderValue();
+
+            if (!TrySetCacheControlValues(result, nameValueList))
+            {
+                return 0;
+            }
+
+            parsedValue = result;
+
+            // If we get here we successfully parsed the whole string.
+            return input.Length - startIndex;
+        }
+
+        private static bool TrySetCacheControlValues(
+            CacheControlHeaderValue cc,
+            List<NameValueHeaderValue> nameValueList)
+        {
+            for (var i = 0; i < nameValueList.Count; i++)
+            {
+                var nameValue = nameValueList[i];
+                var name = nameValue.Name;
+                var success = true;
+
+                switch (name.Length)
+                {
+                    case 6:
+                        if (StringSegment.Equals(PublicString, name, StringComparison.OrdinalIgnoreCase))
+                        {
+                            success = TrySetTokenOnlyValue(nameValue, ref cc._public);
+                        }
+                        else
+                        {
+                            goto default;
+                        }
+                        break;
+
+                    case 7:
+                        if (StringSegment.Equals(MaxAgeString, name, StringComparison.OrdinalIgnoreCase))
+                        {
+                            success = TrySetTimeSpan(nameValue, ref cc._maxAge);
+                        }
+                        else if(StringSegment.Equals(PrivateString, name, StringComparison.OrdinalIgnoreCase))
+                        {
+                            success = TrySetOptionalTokenList(nameValue, ref cc._private, ref cc._privateHeaders);
+                        }
+                        else
+                        {
+                            goto default;
+                        }
+                        break;
+
+                    case 8:
+                        if (StringSegment.Equals(NoCacheString, name, StringComparison.OrdinalIgnoreCase))
+                        {
+                            success = TrySetOptionalTokenList(nameValue, ref cc._noCache, ref cc._noCacheHeaders);
+                        }
+                        else if (StringSegment.Equals(NoStoreString, name, StringComparison.OrdinalIgnoreCase))
+                        {
+                            success = TrySetTokenOnlyValue(nameValue, ref cc._noStore);
+                        }
+                        else if (StringSegment.Equals(SharedMaxAgeString, name, StringComparison.OrdinalIgnoreCase))
+                        {
+                            success = TrySetTimeSpan(nameValue, ref cc._sharedMaxAge);
+                        }
+                        else
+                        {
+                            goto default;
+                        }
+                        break;
+
+                    case 9:
+                        if (StringSegment.Equals(MaxStaleString, name, StringComparison.OrdinalIgnoreCase))
+                        {
+                            success = ((nameValue.Value == null) || TrySetTimeSpan(nameValue, ref cc._maxStaleLimit));
+                            if (success)
+                            {
+                                cc._maxStale = true;
+                            }
+                        }
+                        else if (StringSegment.Equals(MinFreshString, name, StringComparison.OrdinalIgnoreCase))
+                        {
+                            success = TrySetTimeSpan(nameValue, ref cc._minFresh);
+                        }
+                        else
+                        {
+                            goto default;
+                        }
+                        break;
+
+                    case 12:
+                        if (StringSegment.Equals(NoTransformString, name, StringComparison.OrdinalIgnoreCase))
+                        {
+                            success = TrySetTokenOnlyValue(nameValue, ref cc._noTransform);
+                        }
+                        else
+                        {
+                            goto default;
+                        }
+                        break;
+
+                    case 14:
+                        if (StringSegment.Equals(OnlyIfCachedString, name, StringComparison.OrdinalIgnoreCase))
+                        {
+                            success = TrySetTokenOnlyValue(nameValue, ref cc._onlyIfCached);
+                        }
+                        else
+                        {
+                            goto default;
+                        }
+                        break;
+
+                    case 15:
+                        if (StringSegment.Equals(MustRevalidateString, name, StringComparison.OrdinalIgnoreCase))
+                        {
+                            success = TrySetTokenOnlyValue(nameValue, ref cc._mustRevalidate);
+                        }
+                        else
+                        {
+                            goto default;
+                        }
+                        break;
+
+                    case 16:
+                        if (StringSegment.Equals(ProxyRevalidateString, name, StringComparison.OrdinalIgnoreCase))
+                        {
+                            success = TrySetTokenOnlyValue(nameValue, ref cc._proxyRevalidate);
+                        }
+                        else
+                        {
+                            goto default;
+                        }
+                        break;
+
+                    default:
+                        cc.Extensions.Add(nameValue); // success is always true
+                        break;
+                }
+
+                if (!success)
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        private static bool TrySetTokenOnlyValue(NameValueHeaderValue nameValue, ref bool boolField)
+        {
+            if (nameValue.Value != null)
+            {
+                return false;
+            }
+
+            boolField = true;
+            return true;
+        }
+
+        private static bool TrySetOptionalTokenList(
+            NameValueHeaderValue nameValue,
+            ref bool boolField,
+            ref ICollection<StringSegment> destination)
+        {
+            Contract.Requires(nameValue != null);
+
+            if (nameValue.Value == null)
+            {
+                boolField = true;
+                return true;
+            }
+
+            // We need the string to be at least 3 chars long: 2x quotes and at least 1 character. Also make sure we
+            // have a quoted string. Note that NameValueHeaderValue will never have leading/trailing whitespaces.
+            var valueString = nameValue.Value;
+            if ((valueString.Length < 3) || (valueString[0] != '\"') || (valueString[valueString.Length - 1] != '\"'))
+            {
+                return false;
+            }
+
+            // We have a quoted string. Now verify that the string contains a list of valid tokens separated by ','.
+            var current = 1; // skip the initial '"' character.
+            var maxLength = valueString.Length - 1; // -1 because we don't want to parse the final '"'.
+            var separatorFound = false;
+            var originalValueCount = destination == null ? 0 : destination.Count;
+            while (current < maxLength)
+            {
+                current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(valueString, current, true,
+                    out separatorFound);
+
+                if (current == maxLength)
+                {
+                    break;
+                }
+
+                var tokenLength = HttpRuleParser.GetTokenLength(valueString, current);
+
+                if (tokenLength == 0)
+                {
+                    // We already skipped whitespaces and separators. If we don't have a token it must be an invalid
+                    // character.
+                    return false;
+                }
+
+                if (destination == null)
+                {
+                    destination = new ObjectCollection<StringSegment>(CheckIsValidTokenAction);
+                }
+
+                destination.Add(valueString.Subsegment(current, tokenLength));
+
+                current = current + tokenLength;
+            }
+
+            // After parsing a valid token list, we expect to have at least one value
+            if ((destination != null) && (destination.Count > originalValueCount))
+            {
+                boolField = true;
+                return true;
+            }
+
+            return false;
+        }
+
+        private static bool TrySetTimeSpan(NameValueHeaderValue nameValue, ref TimeSpan? timeSpan)
+        {
+            Contract.Requires(nameValue != null);
+
+            if (nameValue.Value == null)
+            {
+                return false;
+            }
+
+            int seconds;
+            if (!HeaderUtilities.TryParseNonNegativeInt32(nameValue.Value, out seconds))
+            {
+                return false;
+            }
+
+            timeSpan = new TimeSpan(0, 0, seconds);
+
+            return true;
+        }
+
+        private static void AppendValueIfRequired(StringBuilder sb, bool appendValue, string value)
+        {
+            if (appendValue)
+            {
+                AppendValueWithSeparatorIfRequired(sb, value);
+            }
+        }
+
+        private static void AppendValueWithSeparatorIfRequired(StringBuilder sb, string value)
+        {
+            if (sb.Length > 0)
+            {
+                sb.Append(", ");
+            }
+            sb.Append(value);
+        }
+
+        private static void AppendValues(StringBuilder sb, IEnumerable<StringSegment> values)
+        {
+            var first = true;
+            foreach (var value in values)
+            {
+                if (first)
+                {
+                    first = false;
+                }
+                else
+                {
+                    sb.Append(", ");
+                }
+
+                sb.Append(value);
+            }
+        }
+
+        private static void CheckIsValidToken(StringSegment item)
+        {
+            HeaderUtilities.CheckValidToken(item, nameof(item));
+        }
+    }
+}
diff --git a/src/Http/Headers/src/ContentDispositionHeaderValue.cs b/src/Http/Headers/src/ContentDispositionHeaderValue.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b9292ac1a8cdeaded5a0a6e38d44a8dd157791ae
--- /dev/null
+++ b/src/Http/Headers/src/ContentDispositionHeaderValue.cs
@@ -0,0 +1,725 @@
+// 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.Buffers;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    // Note this is for use both in HTTP (https://tools.ietf.org/html/rfc6266) and MIME (https://tools.ietf.org/html/rfc2183)
+    public class ContentDispositionHeaderValue
+    {
+        private const string FileNameString = "filename";
+        private const string NameString = "name";
+        private const string FileNameStarString = "filename*";
+        private const string CreationDateString = "creation-date";
+        private const string ModificationDateString = "modification-date";
+        private const string ReadDateString = "read-date";
+        private const string SizeString = "size";
+        private static readonly char[] QuestionMark = new char[] { '?' };
+        private static readonly char[] SingleQuote = new char[] { '\'' };
+
+        private static readonly HttpHeaderParser<ContentDispositionHeaderValue> Parser
+            = new GenericHeaderParser<ContentDispositionHeaderValue>(false, GetDispositionTypeLength);
+
+        // Use list instead of dictionary since we may have multiple parameters with the same name.
+        private ObjectCollection<NameValueHeaderValue> _parameters;
+        private StringSegment _dispositionType;
+
+        private ContentDispositionHeaderValue()
+        {
+            // Used by the parser to create a new instance of this type.
+        }
+
+        public ContentDispositionHeaderValue(StringSegment dispositionType)
+        {
+            CheckDispositionTypeFormat(dispositionType, "dispositionType");
+            _dispositionType = dispositionType;
+        }
+
+        public StringSegment DispositionType
+        {
+            get { return _dispositionType; }
+            set
+            {
+                CheckDispositionTypeFormat(value, "value");
+                _dispositionType = value;
+            }
+        }
+
+        public IList<NameValueHeaderValue> Parameters
+        {
+            get
+            {
+                if (_parameters == null)
+                {
+                    _parameters = new ObjectCollection<NameValueHeaderValue>();
+                }
+                return _parameters;
+            }
+        }
+
+        // Helpers to access specific parameters in the list
+
+        public StringSegment Name
+        {
+            get { return GetName(NameString); }
+            set { SetName(NameString, value); }
+        }
+
+
+        public StringSegment FileName
+        {
+            get { return GetName(FileNameString); }
+            set { SetName(FileNameString, value); }
+        }
+
+        public StringSegment FileNameStar
+        {
+            get { return GetName(FileNameStarString); }
+            set { SetName(FileNameStarString, value); }
+        }
+
+        public DateTimeOffset? CreationDate
+        {
+            get { return GetDate(CreationDateString); }
+            set { SetDate(CreationDateString, value); }
+        }
+
+        public DateTimeOffset? ModificationDate
+        {
+            get { return GetDate(ModificationDateString); }
+            set { SetDate(ModificationDateString, value); }
+        }
+
+        public DateTimeOffset? ReadDate
+        {
+            get { return GetDate(ReadDateString); }
+            set { SetDate(ReadDateString, value); }
+        }
+
+        public long? Size
+        {
+            get
+            {
+                var sizeParameter = NameValueHeaderValue.Find(_parameters, SizeString);
+                long value;
+                if (sizeParameter != null)
+                {
+                    var sizeString = sizeParameter.Value;
+                    if (HeaderUtilities.TryParseNonNegativeInt64(sizeString, out value))
+                    {
+                        return value;
+                    }
+                }
+                return null;
+            }
+            set
+            {
+                var sizeParameter = NameValueHeaderValue.Find(_parameters, SizeString);
+                if (value == null)
+                {
+                    // Remove parameter
+                    if (sizeParameter != null)
+                    {
+                        _parameters.Remove(sizeParameter);
+                    }
+                }
+                else if (value < 0)
+                {
+                    throw new ArgumentOutOfRangeException(nameof(value));
+                }
+                else if (sizeParameter != null)
+                {
+                    sizeParameter.Value = value.Value.ToString(CultureInfo.InvariantCulture);
+                }
+                else
+                {
+                    string sizeString = value.Value.ToString(CultureInfo.InvariantCulture);
+                    _parameters.Add(new NameValueHeaderValue(SizeString, sizeString));
+                }
+            }
+        }
+
+        /// <summary>
+        /// Sets both FileName and FileNameStar using encodings appropriate for HTTP headers.
+        /// </summary>
+        /// <param name="fileName"></param>
+        public void SetHttpFileName(StringSegment fileName)
+        {
+            if (!StringSegment.IsNullOrEmpty(fileName))
+            {
+                FileName = Sanatize(fileName);
+            }
+            else
+            {
+                FileName = fileName;
+            }
+            FileNameStar = fileName;
+        }
+
+        /// <summary>
+        /// Sets the FileName parameter using encodings appropriate for MIME headers.
+        /// The FileNameStar paraemter is removed.
+        /// </summary>
+        /// <param name="fileName"></param>
+        public void SetMimeFileName(StringSegment fileName)
+        {
+            FileNameStar = null;
+            FileName = fileName;
+        }
+
+        public override string ToString()
+        {
+            return _dispositionType + NameValueHeaderValue.ToString(_parameters, ';', true);
+        }
+
+        public override bool Equals(object obj)
+        {
+            var other = obj as ContentDispositionHeaderValue;
+
+            if (other == null)
+            {
+                return false;
+            }
+
+            return _dispositionType.Equals(other._dispositionType, StringComparison.OrdinalIgnoreCase) &&
+                HeaderUtilities.AreEqualCollections(_parameters, other._parameters);
+        }
+
+        public override int GetHashCode()
+        {
+            // The dispositionType string is case-insensitive.
+            return StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_dispositionType) ^ NameValueHeaderValue.GetHashCode(_parameters);
+        }
+
+        public static ContentDispositionHeaderValue Parse(StringSegment input)
+        {
+            var index = 0;
+            return Parser.ParseValue(input, ref index);
+        }
+
+        public static bool TryParse(StringSegment input, out ContentDispositionHeaderValue parsedValue)
+        {
+            var index = 0;
+            return Parser.TryParseValue(input, ref index, out parsedValue);
+        }
+
+        private static int GetDispositionTypeLength(StringSegment input, int startIndex, out ContentDispositionHeaderValue parsedValue)
+        {
+            Contract.Requires(startIndex >= 0);
+
+            parsedValue = null;
+
+            if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+            {
+                return 0;
+            }
+
+            // Caller must remove leading whitespaces. If not, we'll return 0.
+            var dispositionTypeLength = GetDispositionTypeExpressionLength(input, startIndex, out var dispositionType);
+
+            if (dispositionTypeLength == 0)
+            {
+                return 0;
+            }
+
+            var current = startIndex + dispositionTypeLength;
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+            var contentDispositionHeader = new ContentDispositionHeaderValue();
+            contentDispositionHeader._dispositionType = dispositionType;
+
+            // If we're not done and we have a parameter delimiter, then we have a list of parameters.
+            if ((current < input.Length) && (input[current] == ';'))
+            {
+                current++; // skip delimiter.
+                int parameterLength = NameValueHeaderValue.GetNameValueListLength(input, current, ';',
+                    contentDispositionHeader.Parameters);
+
+                parsedValue = contentDispositionHeader;
+                return current + parameterLength - startIndex;
+            }
+
+            // We have a ContentDisposition header without parameters.
+            parsedValue = contentDispositionHeader;
+            return current - startIndex;
+        }
+
+        private static int GetDispositionTypeExpressionLength(StringSegment input, int startIndex, out StringSegment dispositionType)
+        {
+            Contract.Requires((input != null) && (input.Length > 0) && (startIndex < input.Length));
+
+            // This method just parses the disposition type string, it does not parse parameters.
+            dispositionType = null;
+
+            // Parse the disposition type, i.e. <dispositiontype> in content-disposition string
+            // "<dispositiontype>; param1=value1; param2=value2"
+            var typeLength = HttpRuleParser.GetTokenLength(input, startIndex);
+
+            if (typeLength == 0)
+            {
+                return 0;
+            }
+
+            dispositionType = input.Subsegment(startIndex, typeLength);
+            return typeLength;
+        }
+
+        private static void CheckDispositionTypeFormat(StringSegment dispositionType, string parameterName)
+        {
+            if (StringSegment.IsNullOrEmpty(dispositionType))
+            {
+                throw new ArgumentException("An empty string is not allowed.", parameterName);
+            }
+
+            // When adding values using strongly typed objects, no leading/trailing LWS (whitespaces) are allowed.
+            var dispositionTypeLength = GetDispositionTypeExpressionLength(dispositionType, 0, out var tempDispositionType);
+            if ((dispositionTypeLength == 0) || (tempDispositionType.Length != dispositionType.Length))
+            {
+                throw new FormatException(string.Format(CultureInfo.InvariantCulture,
+                    "Invalid disposition type '{0}'.", dispositionType));
+            }
+        }
+
+        // Gets a parameter of the given name and attempts to extract a date.
+        // Returns null if the parameter is not present or the format is incorrect.
+        private DateTimeOffset? GetDate(string parameter)
+        {
+            var dateParameter = NameValueHeaderValue.Find(_parameters, parameter);
+            if (dateParameter != null)
+            {
+                var dateString = dateParameter.Value;
+                // Should have quotes, remove them.
+                if (IsQuoted(dateString))
+                {
+                    dateString = dateString.Subsegment(1, dateString.Length - 2);
+                }
+                DateTimeOffset date;
+                if (HttpRuleParser.TryStringToDate(dateString, out date))
+                {
+                    return date;
+                }
+            }
+            return null;
+        }
+
+        // Add the given parameter to the list. Remove if date is null.
+        private void SetDate(string parameter, DateTimeOffset? date)
+        {
+            var dateParameter = NameValueHeaderValue.Find(_parameters, parameter);
+            if (date == null)
+            {
+                // Remove parameter
+                if (dateParameter != null)
+                {
+                    _parameters.Remove(dateParameter);
+                }
+            }
+            else
+            {
+                // Must always be quoted
+                var dateString = HeaderUtilities.FormatDate(date.Value, quoted: true);
+                if (dateParameter != null)
+                {
+                    dateParameter.Value = dateString;
+                }
+                else
+                {
+                    Parameters.Add(new NameValueHeaderValue(parameter, dateString));
+                }
+            }
+        }
+
+        // Gets a parameter of the given name and attempts to decode it if necessary.
+        // Returns null if the parameter is not present or the raw value if the encoding is incorrect.
+        private StringSegment GetName(string parameter)
+        {
+            var nameParameter = NameValueHeaderValue.Find(_parameters, parameter);
+            if (nameParameter != null)
+            {
+                string result;
+                // filename*=utf-8'lang'%7FMyString
+                if (parameter.EndsWith("*", StringComparison.Ordinal))
+                {
+                    if (TryDecode5987(nameParameter.Value, out result))
+                    {
+                        return result;
+                    }
+                    return null; // Unrecognized encoding
+                }
+
+                // filename="=?utf-8?B?BDFSDFasdfasdc==?="
+                if (TryDecodeMime(nameParameter.Value, out result))
+                {
+                    return result;
+                }
+                // May not have been encoded
+                return HeaderUtilities.RemoveQuotes(nameParameter.Value);
+            }
+            return null;
+        }
+
+        // Add/update the given parameter in the list, encoding if necessary.
+        // Remove if value is null/Empty
+        private void SetName(StringSegment parameter, StringSegment value)
+        {
+            var nameParameter = NameValueHeaderValue.Find(_parameters, parameter);
+            if (StringSegment.IsNullOrEmpty(value))
+            {
+                // Remove parameter
+                if (nameParameter != null)
+                {
+                    _parameters.Remove(nameParameter);
+                }
+            }
+            else
+            {
+                var processedValue = StringSegment.Empty;
+                if (parameter.EndsWith("*", StringComparison.Ordinal))
+                {
+                    processedValue = Encode5987(value);
+                }
+                else
+                {
+                    processedValue = EncodeAndQuoteMime(value);
+                }
+
+                if (nameParameter != null)
+                {
+                    nameParameter.Value = processedValue;
+                }
+                else
+                {
+                    Parameters.Add(new NameValueHeaderValue(parameter, processedValue));
+                }
+            }
+        }
+
+        // Returns input for decoding failures, as the content might not be encoded
+        private StringSegment EncodeAndQuoteMime(StringSegment input)
+        {
+            var result = input;
+            var needsQuotes = false;
+            // Remove bounding quotes, they'll get re-added later
+            if (IsQuoted(result))
+            {
+                result = result.Subsegment(1, result.Length - 2);
+                needsQuotes = true;
+            }
+
+            if (RequiresEncoding(result))
+            {
+                needsQuotes = true; // Encoded data must always be quoted, the equals signs are invalid in tokens
+                result = EncodeMime(result); // =?utf-8?B?asdfasdfaesdf?=
+            }
+            else if (!needsQuotes && HttpRuleParser.GetTokenLength(result, 0) != result.Length)
+            {
+                needsQuotes = true;
+            }
+
+            if (needsQuotes)
+            {
+                // '\' and '"' must be escaped in a quoted string
+                result = result.ToString().Replace(@"\", @"\\").Replace(@"""", @"\""");
+                // Re-add quotes "value"
+                result = string.Format(CultureInfo.InvariantCulture, "\"{0}\"", result);
+            }
+            return result;
+        }
+
+        // Replaces characters not suitable for HTTP headers with '_' rather than MIME encoding them.
+        private StringSegment Sanatize(StringSegment input)
+        {
+            var result = input;
+
+            if (RequiresEncoding(result))
+            {
+                var builder = new StringBuilder(result.Length);
+                for (int i = 0; i < result.Length; i++)
+                {
+                    var c = result[i];
+                    if ((int)c > 0x7f)
+                    {
+                        c = '_'; // Replace out-of-range characters
+                    }
+                    builder.Append(c);
+                }
+                result = builder.ToString();
+            }
+
+            return result;
+        }
+
+        // Returns true if the value starts and ends with a quote
+        private bool IsQuoted(StringSegment value)
+        {
+            Contract.Assert(value != null);
+
+            return value.Length > 1 && value.StartsWith("\"", StringComparison.Ordinal)
+                && value.EndsWith("\"", StringComparison.Ordinal);
+        }
+
+        // tspecials are required to be in a quoted string.  Only non-ascii needs to be encoded.
+        private bool RequiresEncoding(StringSegment input)
+        {
+            Contract.Assert(input != null);
+
+            for (int i = 0; i < input.Length; i++)
+            {
+                if ((int)input[i] > 0x7f)
+                {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        // Encode using MIME encoding
+        private unsafe string EncodeMime(StringSegment input)
+        {
+            fixed (char* chars = input.Buffer)
+            {
+                var byteCount = Encoding.UTF8.GetByteCount(chars + input.Offset, input.Length);
+                var buffer = new byte[byteCount];
+                fixed (byte* bytes = buffer)
+                {
+                    Encoding.UTF8.GetBytes(chars + input.Offset, input.Length, bytes, byteCount);
+                }
+                var encodedName = Convert.ToBase64String(buffer);
+                return "=?utf-8?B?" + encodedName + "?=";
+            }
+        }
+
+        // Attempt to decode MIME encoded strings
+        private bool TryDecodeMime(StringSegment input, out string output)
+        {
+            Contract.Assert(input != null);
+
+            output = null;
+            var processedInput = input;
+            // Require quotes, min of "=?e?b??="
+            if (!IsQuoted(processedInput) || processedInput.Length < 10)
+            {
+                return false;
+            }
+
+            var parts = processedInput.Split(QuestionMark).ToArray();
+            // "=, encodingName, encodingType, encodedData, ="
+            if (parts.Length != 5 || parts[0] != "\"=" || parts[4] != "=\""
+                || !parts[2].Equals("b", StringComparison.OrdinalIgnoreCase))
+            {
+                // Not encoded.
+                // This does not support multi-line encoding.
+                // Only base64 encoding is supported, not quoted printable
+                return false;
+            }
+
+            try
+            {
+                var encoding = Encoding.GetEncoding(parts[1].ToString());
+                var bytes = Convert.FromBase64String(parts[3].ToString());
+                output = encoding.GetString(bytes, 0, bytes.Length);
+                return true;
+            }
+            catch (ArgumentException)
+            {
+                // Unknown encoding or bad characters
+            }
+            catch (FormatException)
+            {
+                // Bad base64 decoding
+            }
+            return false;
+        }
+
+        // Encode a string using RFC 5987 encoding
+        // encoding'lang'PercentEncodedSpecials
+        private string Encode5987(StringSegment input)
+        {
+            var builder = new StringBuilder("UTF-8\'\'");
+            for (int i = 0; i < input.Length; i++)
+            {
+                var c = input[i];
+                // attr-char = ALPHA / DIGIT / "!" / "#" / "$" / "&" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
+                //      ; token except ( "*" / "'" / "%" )
+                if (c > 0x7F) // Encodes as multiple utf-8 bytes
+                {
+                    var bytes = Encoding.UTF8.GetBytes(c.ToString());
+                    foreach (byte b in bytes)
+                    {
+                        HexEscape(builder, (char)b);
+                    }
+                }
+                else if (!HttpRuleParser.IsTokenChar(c) || c == '*' || c == '\'' || c == '%')
+                {
+                    // ASCII - Only one encoded byte
+                    HexEscape(builder, c);
+                }
+                else
+                {
+                    builder.Append(c);
+                }
+            }
+            return builder.ToString();
+        }
+
+        private static readonly char[] HexUpperChars = {
+                                   '0', '1', '2', '3', '4', '5', '6', '7',
+                                   '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+        private static void HexEscape(StringBuilder builder, char c)
+        {
+            builder.Append('%');
+            builder.Append(HexUpperChars[(c & 0xf0) >> 4]);
+            builder.Append(HexUpperChars[c & 0xf]);
+        }
+
+        // Attempt to decode using RFC 5987 encoding.
+        // encoding'language'my%20string
+        private bool TryDecode5987(StringSegment input, out string output)
+        {
+            output = null;
+
+            var parts = input.Split(SingleQuote).ToArray();
+            if (parts.Length != 3)
+            {
+                return false;
+            }
+
+            var decoded = new StringBuilder();
+            byte[] unescapedBytes = null;
+            try
+            {
+                var encoding = Encoding.GetEncoding(parts[0].ToString());
+
+                var dataString = parts[2];
+                unescapedBytes = ArrayPool<byte>.Shared.Rent(dataString.Length);
+                var unescapedBytesCount = 0;
+                for (var index = 0; index < dataString.Length; index++)
+                {
+                    if (IsHexEncoding(dataString, index)) // %FF
+                    {
+                        // Unescape and cache bytes, multi-byte characters must be decoded all at once
+                        unescapedBytes[unescapedBytesCount++] = HexUnescape(dataString, ref index);
+                        index--; // HexUnescape did +=3; Offset the for loop's ++
+                    }
+                    else
+                    {
+                        if (unescapedBytesCount > 0)
+                        {
+                            // Decode any previously cached bytes
+                            decoded.Append(encoding.GetString(unescapedBytes, 0, unescapedBytesCount));
+                            unescapedBytesCount = 0;
+                        }
+                        decoded.Append(dataString[index]); // Normal safe character
+                    }
+                }
+
+                if (unescapedBytesCount > 0)
+                {
+                    // Decode any previously cached bytes
+                    decoded.Append(encoding.GetString(unescapedBytes, 0, unescapedBytesCount));
+                }
+            }
+            catch (ArgumentException)
+            {
+                return false; // Unknown encoding or bad characters
+            }
+            finally
+            {
+                if (unescapedBytes != null)
+                {
+                    ArrayPool<byte>.Shared.Return(unescapedBytes);
+                }
+            }
+
+            output = decoded.ToString();
+            return true;
+        }
+
+        private static bool IsHexEncoding(StringSegment pattern, int index)
+        {
+            if ((pattern.Length - index) < 3)
+            {
+                return false;
+            }
+            if ((pattern[index] == '%') && IsEscapedAscii(pattern[index + 1], pattern[index + 2]))
+            {
+                return true;
+            }
+            return false;
+        }
+
+        private static bool IsEscapedAscii(char digit, char next)
+        {
+            if (!(((digit >= '0') && (digit <= '9'))
+                || ((digit >= 'A') && (digit <= 'F'))
+                || ((digit >= 'a') && (digit <= 'f'))))
+            {
+                return false;
+            }
+
+            if (!(((next >= '0') && (next <= '9'))
+                || ((next >= 'A') && (next <= 'F'))
+                || ((next >= 'a') && (next <= 'f'))))
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        private static byte HexUnescape(StringSegment pattern, ref int index)
+        {
+            if ((index < 0) || (index >= pattern.Length))
+            {
+                throw new ArgumentOutOfRangeException(nameof(index));
+            }
+            if ((pattern[index] == '%')
+                && (pattern.Length - index >= 3))
+            {
+                var ret = UnEscapeAscii(pattern[index + 1], pattern[index + 2]);
+                index += 3;
+                return ret;
+            }
+            return (byte)pattern[index++];
+        }
+
+        internal static byte UnEscapeAscii(char digit, char next)
+        {
+            if (!(((digit >= '0') && (digit <= '9'))
+                || ((digit >= 'A') && (digit <= 'F'))
+                || ((digit >= 'a') && (digit <= 'f'))))
+            {
+                throw new ArgumentException();
+            }
+
+            var res = (digit <= '9')
+                ? ((int)digit - (int)'0')
+                : (((digit <= 'F')
+                ? ((int)digit - (int)'A')
+                : ((int)digit - (int)'a'))
+                   + 10);
+
+            if (!(((next >= '0') && (next <= '9'))
+                || ((next >= 'A') && (next <= 'F'))
+                || ((next >= 'a') && (next <= 'f'))))
+            {
+                throw new ArgumentException();
+            }
+
+            return (byte)((res << 4) + ((next <= '9')
+                    ? ((int)next - (int)'0')
+                    : (((next <= 'F')
+                        ? ((int)next - (int)'A')
+                        : ((int)next - (int)'a'))
+                       + 10)));
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Headers/src/ContentDispositionHeaderValueIdentityExtensions.cs b/src/Http/Headers/src/ContentDispositionHeaderValueIdentityExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9ef74baa0c11b8459cd783528684e1be24063bde
--- /dev/null
+++ b/src/Http/Headers/src/ContentDispositionHeaderValueIdentityExtensions.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 Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    /// <summary>
+    /// Various extension methods for <see cref="ContentDispositionHeaderValue"/> for identifying the type of the disposition header
+    /// </summary>
+    public static class ContentDispositionHeaderValueIdentityExtensions
+    {
+        /// <summary>
+        /// Checks if the content disposition header is a file disposition
+        /// </summary>
+        /// <param name="header">The header to check</param>
+        /// <returns>True if the header is file disposition, false otherwise</returns>
+        public static bool IsFileDisposition(this ContentDispositionHeaderValue header)
+        {
+            if (header == null)
+            {
+                throw new ArgumentNullException(nameof(header));
+            }
+
+            return header.DispositionType.Equals("form-data")
+                && (!StringSegment.IsNullOrEmpty(header.FileName) || !StringSegment.IsNullOrEmpty(header.FileNameStar));
+        }
+
+        /// <summary>
+        /// Checks if the content disposition header is a form disposition
+        /// </summary>
+        /// <param name="header">The header to check</param>
+        /// <returns>True if the header is form disposition, false otherwise</returns>
+        public static bool IsFormDisposition(this ContentDispositionHeaderValue header)
+        {
+            if (header == null)
+            {
+                throw new ArgumentNullException(nameof(header));
+            }
+
+            return header.DispositionType.Equals("form-data")
+               && StringSegment.IsNullOrEmpty(header.FileName) && StringSegment.IsNullOrEmpty(header.FileNameStar);
+        }
+    }
+}
diff --git a/src/Http/Headers/src/ContentRangeHeaderValue.cs b/src/Http/Headers/src/ContentRangeHeaderValue.cs
new file mode 100644
index 0000000000000000000000000000000000000000..99583cdf47039cf4cbe352d40d040f0fac4348d6
--- /dev/null
+++ b/src/Http/Headers/src/ContentRangeHeaderValue.cs
@@ -0,0 +1,407 @@
+// 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.Contracts;
+using System.Globalization;
+using System.Text;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class ContentRangeHeaderValue
+    {
+        private static readonly HttpHeaderParser<ContentRangeHeaderValue> Parser
+            = new GenericHeaderParser<ContentRangeHeaderValue>(false, GetContentRangeLength);
+
+        private StringSegment _unit;
+        private long? _from;
+        private long? _to;
+        private long? _length;
+
+        private ContentRangeHeaderValue()
+        {
+            // Used by the parser to create a new instance of this type.
+        }
+
+        public ContentRangeHeaderValue(long from, long to, long length)
+        {
+            // Scenario: "Content-Range: bytes 12-34/5678"
+
+            if (length < 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(length));
+            }
+            if ((to < 0) || (to > length))
+            {
+                throw new ArgumentOutOfRangeException(nameof(to));
+            }
+            if ((from < 0) || (from > to))
+            {
+                throw new ArgumentOutOfRangeException(nameof(from));
+            }
+
+            _from = from;
+            _to = to;
+            _length = length;
+            _unit = HeaderUtilities.BytesUnit;
+        }
+
+        public ContentRangeHeaderValue(long length)
+        {
+            // Scenario: "Content-Range: bytes */1234"
+
+            if (length < 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(length));
+            }
+
+            _length = length;
+            _unit = HeaderUtilities.BytesUnit;
+        }
+
+        public ContentRangeHeaderValue(long from, long to)
+        {
+            // Scenario: "Content-Range: bytes 12-34/*"
+
+            if (to < 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(to));
+            }
+            if ((from < 0) || (from > to))
+            {
+                throw new ArgumentOutOfRangeException(nameof(@from));
+            }
+
+            _from = from;
+            _to = to;
+            _unit = HeaderUtilities.BytesUnit;
+        }
+
+        public StringSegment Unit
+        {
+            get { return _unit; }
+            set
+            {
+                HeaderUtilities.CheckValidToken(value, nameof(value));
+                _unit = value;
+            }
+        }
+
+        public long? From
+        {
+            get { return _from; }
+        }
+
+        public long? To
+        {
+            get { return _to; }
+        }
+
+        public long? Length
+        {
+            get { return _length; }
+        }
+
+        public bool HasLength // e.g. "Content-Range: bytes 12-34/*"
+        {
+            get { return _length != null; }
+        }
+
+        public bool HasRange // e.g. "Content-Range: bytes */1234"
+        {
+            get { return _from != null; }
+        }
+
+        public override bool Equals(object obj)
+        {
+            var other = obj as ContentRangeHeaderValue;
+
+            if (other == null)
+            {
+                return false;
+            }
+
+            return ((_from == other._from) && (_to == other._to) && (_length == other._length) &&
+                StringSegment.Equals(_unit, other._unit, StringComparison.OrdinalIgnoreCase));
+        }
+
+        public override int GetHashCode()
+        {
+            var result = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_unit);
+
+            if (HasRange)
+            {
+                result = result ^ _from.GetHashCode() ^ _to.GetHashCode();
+            }
+
+            if (HasLength)
+            {
+                result = result ^ _length.GetHashCode();
+            }
+
+            return result;
+        }
+
+        public override string ToString()
+        {
+            var sb = new StringBuilder();
+            sb.Append(_unit);
+            sb.Append(' ');
+
+            if (HasRange)
+            {
+                sb.Append(_from.Value.ToString(NumberFormatInfo.InvariantInfo));
+                sb.Append('-');
+                sb.Append(_to.Value.ToString(NumberFormatInfo.InvariantInfo));
+            }
+            else
+            {
+                sb.Append('*');
+            }
+
+            sb.Append('/');
+            if (HasLength)
+            {
+                sb.Append(_length.Value.ToString(NumberFormatInfo.InvariantInfo));
+            }
+            else
+            {
+                sb.Append('*');
+            }
+
+            return sb.ToString();
+        }
+
+        public static ContentRangeHeaderValue Parse(StringSegment input)
+        {
+            var index = 0;
+            return Parser.ParseValue(input, ref index);
+        }
+
+        public static bool TryParse(StringSegment input, out ContentRangeHeaderValue parsedValue)
+        {
+            var index = 0;
+            return Parser.TryParseValue(input, ref index, out parsedValue);
+        }
+
+        private static int GetContentRangeLength(StringSegment input, int startIndex, out ContentRangeHeaderValue parsedValue)
+        {
+            Contract.Requires(startIndex >= 0);
+
+            parsedValue = null;
+
+            if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+            {
+                return 0;
+            }
+
+            // Parse the unit string: <unit> in '<unit> <from>-<to>/<length>'
+            var unitLength = HttpRuleParser.GetTokenLength(input, startIndex);
+
+            if (unitLength == 0)
+            {
+                return 0;
+            }
+
+            var unit = input.Subsegment(startIndex, unitLength);
+            var current = startIndex + unitLength;
+            var separatorLength = HttpRuleParser.GetWhitespaceLength(input, current);
+
+            if (separatorLength == 0)
+            {
+                return 0;
+            }
+
+            current = current + separatorLength;
+
+            if (current == input.Length)
+            {
+                return 0;
+            }
+
+            // Read range values <from> and <to> in '<unit> <from>-<to>/<length>'
+            var fromStartIndex = current;
+            var fromLength = 0;
+            var toStartIndex = 0;
+            var toLength = 0;
+            if (!TryGetRangeLength(input, ref current, out fromLength, out toStartIndex, out toLength))
+            {
+                return 0;
+            }
+
+            // After the range is read we expect the length separator '/'
+            if ((current == input.Length) || (input[current] != '/'))
+            {
+                return 0;
+            }
+
+            current++; // Skip '/' separator
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+            if (current == input.Length)
+            {
+                return 0;
+            }
+
+            // We may not have a length (e.g. 'bytes 1-2/*'). But if we do, parse the length now.
+            var lengthStartIndex = current;
+            var lengthLength = 0;
+            if (!TryGetLengthLength(input, ref current, out lengthLength))
+            {
+                return 0;
+            }
+
+            if (!TryCreateContentRange(input, unit, fromStartIndex, fromLength, toStartIndex, toLength,
+                lengthStartIndex, lengthLength, out parsedValue))
+            {
+                return 0;
+            }
+
+            return current - startIndex;
+        }
+
+        private static bool TryGetLengthLength(StringSegment input, ref int current, out int lengthLength)
+        {
+            lengthLength = 0;
+
+            if (input[current] == '*')
+            {
+                current++;
+            }
+            else
+            {
+                // Parse length value: <length> in '<unit> <from>-<to>/<length>'
+                lengthLength = HttpRuleParser.GetNumberLength(input, current, false);
+
+                if ((lengthLength == 0) || (lengthLength > HttpRuleParser.MaxInt64Digits))
+                {
+                    return false;
+                }
+
+                current = current + lengthLength;
+            }
+
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+            return true;
+        }
+
+        private static bool TryGetRangeLength(StringSegment input, ref int current, out int fromLength, out int toStartIndex, out int toLength)
+        {
+            fromLength = 0;
+            toStartIndex = 0;
+            toLength = 0;
+
+            // Check if we have a value like 'bytes */133'. If yes, skip the range part and continue parsing the
+            // length separator '/'.
+            if (input[current] == '*')
+            {
+                current++;
+            }
+            else
+            {
+                // Parse first range value: <from> in '<unit> <from>-<to>/<length>'
+                fromLength = HttpRuleParser.GetNumberLength(input, current, false);
+
+                if ((fromLength == 0) || (fromLength > HttpRuleParser.MaxInt64Digits))
+                {
+                    return false;
+                }
+
+                current = current + fromLength;
+                current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+                // After the first value, the '-' character must follow.
+                if ((current == input.Length) || (input[current] != '-'))
+                {
+                    // We need a '-' character otherwise this can't be a valid range.
+                    return false;
+                }
+
+                current++; // skip the '-' character
+                current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+                if (current == input.Length)
+                {
+                    return false;
+                }
+
+                // Parse second range value: <to> in '<unit> <from>-<to>/<length>'
+                toStartIndex = current;
+                toLength = HttpRuleParser.GetNumberLength(input, current, false);
+
+                if ((toLength == 0) || (toLength > HttpRuleParser.MaxInt64Digits))
+                {
+                    return false;
+                }
+
+                current = current + toLength;
+            }
+
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+            return true;
+        }
+
+        private static bool TryCreateContentRange(
+            StringSegment input,
+            StringSegment unit,
+            int fromStartIndex,
+            int fromLength,
+            int toStartIndex,
+            int toLength,
+            int lengthStartIndex,
+            int lengthLength,
+            out ContentRangeHeaderValue parsedValue)
+        {
+            parsedValue = null;
+
+            long from = 0;
+            if ((fromLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Subsegment(fromStartIndex, fromLength), out from))
+            {
+                return false;
+            }
+
+            long to = 0;
+            if ((toLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Subsegment(toStartIndex, toLength), out to))
+            {
+                return false;
+            }
+
+            // 'from' must not be greater than 'to'
+            if ((fromLength > 0) && (toLength > 0) && (from > to))
+            {
+                return false;
+            }
+
+            long length = 0;
+            if ((lengthLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Subsegment(lengthStartIndex, lengthLength),
+                out length))
+            {
+                return false;
+            }
+
+            // 'from' and 'to' must be less than 'length'
+            if ((toLength > 0) && (lengthLength > 0) && (to >= length))
+            {
+                return false;
+            }
+
+            var result = new ContentRangeHeaderValue();
+            result._unit = unit;
+
+            if (fromLength > 0)
+            {
+                result._from = from;
+                result._to = to;
+            }
+
+            if (lengthLength > 0)
+            {
+                result._length = length;
+            }
+
+            parsedValue = result;
+            return true;
+        }
+    }
+}
diff --git a/src/Http/Headers/src/CookieHeaderParser.cs b/src/Http/Headers/src/CookieHeaderParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a94b61d319e946519d8bb87f1cb2ac5880615c3d
--- /dev/null
+++ b/src/Http/Headers/src/CookieHeaderParser.cs
@@ -0,0 +1,98 @@
+// 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.Contracts;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    internal class CookieHeaderParser : HttpHeaderParser<CookieHeaderValue>
+    {
+        internal CookieHeaderParser(bool supportsMultipleValues)
+            : base(supportsMultipleValues)
+        {
+        }
+
+        public sealed override bool TryParseValue(StringSegment value, ref int index, out CookieHeaderValue parsedValue)
+        {
+            parsedValue = null;
+
+            // If multiple values are supported (i.e. list of values), then accept an empty string: The header may
+            // be added multiple times to the request/response message. E.g.
+            //  Accept: text/xml; q=1
+            //  Accept:
+            //  Accept: text/plain; q=0.2
+            if (StringSegment.IsNullOrEmpty(value) || (index == value.Length))
+            {
+                return SupportsMultipleValues;
+            }
+
+            var separatorFound = false;
+            var current = GetNextNonEmptyOrWhitespaceIndex(value, index, SupportsMultipleValues, out separatorFound);
+
+            if (separatorFound && !SupportsMultipleValues)
+            {
+                return false; // leading separators not allowed if we don't support multiple values.
+            }
+
+            if (current == value.Length)
+            {
+                if (SupportsMultipleValues)
+                {
+                    index = current;
+                }
+                return SupportsMultipleValues;
+            }
+
+            CookieHeaderValue result = null;
+            if (!CookieHeaderValue.TryGetCookieLength(value, ref current, out result))
+            {
+                return false;
+            }
+
+            current = GetNextNonEmptyOrWhitespaceIndex(value, current, SupportsMultipleValues, out separatorFound);
+
+            // If we support multiple values and we've not reached the end of the string, then we must have a separator.
+            if ((separatorFound && !SupportsMultipleValues) || (!separatorFound && (current < value.Length)))
+            {
+                return false;
+            }
+
+            index = current;
+            parsedValue = result;
+            return true;
+        }
+
+        private static int GetNextNonEmptyOrWhitespaceIndex(StringSegment input, int startIndex, bool skipEmptyValues, out bool separatorFound)
+        {
+            Contract.Requires(input != null);
+            Contract.Requires(startIndex <= input.Length); // it's OK if index == value.Length.
+
+            separatorFound = false;
+            var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex);
+
+            if ((current == input.Length) || (input[current] != ',' && input[current] != ';'))
+            {
+                return current;
+            }
+
+            // If we have a separator, skip the separator and all following whitespaces. If we support
+            // empty values, continue until the current character is neither a separator nor a whitespace.
+            separatorFound = true;
+            current++; // skip delimiter.
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+            if (skipEmptyValues)
+            {
+                // Most headers only split on ',', but cookies primarily split on ';'
+                while ((current < input.Length) && ((input[current] == ',') || (input[current] == ';')))
+                {
+                    current++; // skip delimiter.
+                    current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+                }
+            }
+
+            return current;
+        }
+    }
+}
diff --git a/src/Http/Headers/src/CookieHeaderValue.cs b/src/Http/Headers/src/CookieHeaderValue.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3061b7d2fa56e5f79f7a81f3bedb39dea57da8fa
--- /dev/null
+++ b/src/Http/Headers/src/CookieHeaderValue.cs
@@ -0,0 +1,277 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Text;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    // http://tools.ietf.org/html/rfc6265
+    public class CookieHeaderValue
+    {
+        private static readonly CookieHeaderParser SingleValueParser = new CookieHeaderParser(supportsMultipleValues: false);
+        private static readonly CookieHeaderParser MultipleValueParser = new CookieHeaderParser(supportsMultipleValues: true);
+
+        private StringSegment _name;
+        private StringSegment _value;
+
+        private CookieHeaderValue()
+        {
+            // Used by the parser to create a new instance of this type.
+        }
+
+        public CookieHeaderValue(StringSegment name)
+            : this(name, StringSegment.Empty)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+        }
+
+        public CookieHeaderValue(StringSegment name, StringSegment value)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            if (value == null)
+            {
+                throw new ArgumentNullException(nameof(value));
+            }
+
+            Name = name;
+            Value = value;
+        }
+
+        public StringSegment Name
+        {
+            get { return _name; }
+            set
+            {
+                CheckNameFormat(value, nameof(value));
+                _name = value;
+            }
+        }
+
+        public StringSegment Value
+        {
+            get { return _value; }
+            set
+            {
+                CheckValueFormat(value, nameof(value));
+                _value = value;
+            }
+        }
+
+        // name="val ue";
+        public override string ToString()
+        {
+            var header = new StringBuilder();
+
+            header.Append(_name);
+            header.Append("=");
+            header.Append(_value);
+
+            return header.ToString();
+        }
+
+        public static CookieHeaderValue Parse(StringSegment input)
+        {
+            var index = 0;
+            return SingleValueParser.ParseValue(input, ref index);
+        }
+
+        public static bool TryParse(StringSegment input, out CookieHeaderValue parsedValue)
+        {
+            var index = 0;
+            return SingleValueParser.TryParseValue(input, ref index, out parsedValue);
+        }
+
+        public static IList<CookieHeaderValue> ParseList(IList<string> inputs)
+        {
+            return MultipleValueParser.ParseValues(inputs);
+        }
+
+        public static IList<CookieHeaderValue> ParseStrictList(IList<string> inputs)
+        {
+            return MultipleValueParser.ParseStrictValues(inputs);
+        }
+
+        public static bool TryParseList(IList<string> inputs, out IList<CookieHeaderValue> parsedValues)
+        {
+            return MultipleValueParser.TryParseValues(inputs, out parsedValues);
+        }
+
+        public static bool TryParseStrictList(IList<string> inputs, out IList<CookieHeaderValue> parsedValues)
+        {
+            return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
+        }
+
+        // name=value; name="value"
+        internal static bool TryGetCookieLength(StringSegment input, ref int offset, out CookieHeaderValue parsedValue)
+        {
+            Contract.Requires(offset >= 0);
+
+            parsedValue = null;
+
+            if (StringSegment.IsNullOrEmpty(input) || (offset >= input.Length))
+            {
+                return false;
+            }
+
+            var result = new CookieHeaderValue();
+
+            // The caller should have already consumed any leading whitespace, commas, etc..
+
+            // Name=value;
+
+            // Name
+            var itemLength = HttpRuleParser.GetTokenLength(input, offset);
+            if (itemLength == 0)
+            {
+                return false;
+            }
+            result._name = input.Subsegment(offset, itemLength);
+            offset += itemLength;
+
+            // = (no spaces)
+            if (!ReadEqualsSign(input, ref offset))
+            {
+                return false;
+            }
+
+            // value or "quoted value"
+            // The value may be empty
+            result._value = GetCookieValue(input, ref offset);
+
+            parsedValue = result;
+            return true;
+        }
+
+        // cookie-value      = *cookie-octet / ( DQUOTE* cookie-octet DQUOTE )
+        // cookie-octet      = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
+        //                     ; US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash
+        internal static StringSegment GetCookieValue(StringSegment input, ref int offset)
+        {
+            Contract.Requires(input != null);
+            Contract.Requires(offset >= 0);
+            Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - offset)));
+
+            var startIndex = offset;
+
+            if (offset >= input.Length)
+            {
+                return StringSegment.Empty;
+            }
+            var inQuotes = false;
+
+            if (input[offset] == '"')
+            {
+                inQuotes = true;
+                offset++;
+            }
+
+            while (offset < input.Length)
+            {
+                var c = input[offset];
+                if (!IsCookieValueChar(c))
+                {
+                    break;
+                }
+
+                offset++;
+            }
+
+            if (inQuotes)
+            {
+                if (offset == input.Length || input[offset] != '"')
+                {
+                    // Missing final quote
+                    return StringSegment.Empty;
+                }
+                offset++;
+            }
+
+            int length = offset - startIndex;
+            if (offset > startIndex)
+            {
+                return input.Subsegment(startIndex, length);
+            }
+
+            return StringSegment.Empty;
+        }
+
+        private static bool ReadEqualsSign(StringSegment input, ref int offset)
+        {
+            // = (no spaces)
+            if (offset >= input.Length || input[offset] != '=')
+            {
+                return false;
+            }
+            offset++;
+            return true;
+        }
+
+        // cookie-octet      = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
+        //                     ; US-ASCII characters excluding CTLs, whitespace DQUOTE, comma, semicolon, and backslash
+        private static bool IsCookieValueChar(char c)
+        {
+            if (c < 0x21 || c > 0x7E)
+            {
+                return false;
+            }
+            return !(c == '"' || c == ',' || c == ';' || c == '\\');
+        }
+
+        internal static void CheckNameFormat(StringSegment name, string parameterName)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            if (HttpRuleParser.GetTokenLength(name, 0) != name.Length)
+            {
+                throw new ArgumentException("Invalid cookie name: " + name, parameterName);
+            }
+        }
+
+        internal static void CheckValueFormat(StringSegment value, string parameterName)
+        {
+            if (value == null)
+            {
+                throw new ArgumentNullException(nameof(value));
+            }
+
+            var offset = 0;
+            var result = GetCookieValue(value, ref offset);
+            if (result.Length != value.Length)
+            {
+                throw new ArgumentException("Invalid cookie value: " + value, parameterName);
+            }
+        }
+
+        public override bool Equals(object obj)
+        {
+            var other = obj as CookieHeaderValue;
+
+            if (other == null)
+            {
+                return false;
+            }
+
+            return StringSegment.Equals(_name, other._name, StringComparison.OrdinalIgnoreCase)
+                && StringSegment.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase);
+        }
+
+        public override int GetHashCode()
+        {
+            return _name.GetHashCode() ^ _value.GetHashCode();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Headers/src/DateTimeFormatter.cs b/src/Http/Headers/src/DateTimeFormatter.cs
new file mode 100644
index 0000000000000000000000000000000000000000..06893155bd3db1082209fc3de64331e9b815dd02
--- /dev/null
+++ b/src/Http/Headers/src/DateTimeFormatter.cs
@@ -0,0 +1,100 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Globalization;
+using System.Runtime.CompilerServices;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    internal static class DateTimeFormatter
+    {
+        private static readonly DateTimeFormatInfo FormatInfo = CultureInfo.InvariantCulture.DateTimeFormat;
+
+        private static readonly string[] MonthNames = FormatInfo.AbbreviatedMonthNames;
+        private static readonly string[] DayNames = FormatInfo.AbbreviatedDayNames;
+
+        private static readonly int Rfc1123DateLength = "ddd, dd MMM yyyy HH:mm:ss GMT".Length;
+        private static readonly int QuotedRfc1123DateLength = Rfc1123DateLength + 2;
+
+        // ASCII numbers are in the range 48 - 57.
+        private const int AsciiNumberOffset = 0x30;
+
+        private const string Gmt = "GMT";
+        private const char Comma = ',';
+        private const char Space = ' ';
+        private const char Colon = ':';
+        private const char Quote = '"';
+
+        public static string ToRfc1123String(this DateTimeOffset dateTime)
+        {
+            return ToRfc1123String(dateTime, false);
+        }
+
+        public static string ToRfc1123String(this DateTimeOffset dateTime, bool quoted)
+        {
+            var universal = dateTime.UtcDateTime;
+
+            var length = quoted ? QuotedRfc1123DateLength : Rfc1123DateLength;
+            var target = new InplaceStringBuilder(length);
+
+            if (quoted)
+            {
+                target.Append(Quote);
+            }
+
+            target.Append(DayNames[(int)universal.DayOfWeek]);
+            target.Append(Comma);
+            target.Append(Space);
+            AppendNumber(ref target, universal.Day);
+            target.Append(Space);
+            target.Append(MonthNames[universal.Month - 1]);
+            target.Append(Space);
+            AppendYear(ref target, universal.Year);
+            target.Append(Space);
+            AppendTimeOfDay(ref target, universal.TimeOfDay);
+            target.Append(Space);
+            target.Append(Gmt);
+
+            if (quoted)
+            {
+                target.Append(Quote);
+            }
+
+            return target.ToString();
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void AppendYear(ref InplaceStringBuilder target, int year)
+        {
+            target.Append(GetAsciiChar(year / 1000));
+            target.Append(GetAsciiChar(year % 1000 / 100));
+            target.Append(GetAsciiChar(year % 100 / 10));
+            target.Append(GetAsciiChar(year % 10));
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void AppendTimeOfDay(ref InplaceStringBuilder target, TimeSpan timeOfDay)
+        {
+            AppendNumber(ref target, timeOfDay.Hours);
+            target.Append(Colon);
+            AppendNumber(ref target, timeOfDay.Minutes);
+            target.Append(Colon);
+            AppendNumber(ref target, timeOfDay.Seconds);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void AppendNumber(ref InplaceStringBuilder target, int number)
+        {
+            target.Append(GetAsciiChar(number / 10));
+            target.Append(GetAsciiChar(number % 10));
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static char GetAsciiChar(int value)
+        {
+            return (char)(AsciiNumberOffset + value);
+        }
+    }
+}
diff --git a/src/Http/Headers/src/EntityTagHeaderValue.cs b/src/Http/Headers/src/EntityTagHeaderValue.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e46cee3a34ced46be4d81851fa95b08986944819
--- /dev/null
+++ b/src/Http/Headers/src/EntityTagHeaderValue.cs
@@ -0,0 +1,250 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class EntityTagHeaderValue
+    {
+        // Note that the ETag header does not allow a * but we're not that strict: We allow both '*' and ETag values in a single value.
+        // We can't guarantee that a single parsed value will be used directly in an ETag header.
+        private static readonly HttpHeaderParser<EntityTagHeaderValue> SingleValueParser
+            = new GenericHeaderParser<EntityTagHeaderValue>(false, GetEntityTagLength);
+        // Note that if multiple ETag values are allowed (e.g. 'If-Match', 'If-None-Match'), according to the RFC
+        // the value must either be '*' or a list of ETag values. It's not allowed to have both '*' and a list of
+        // ETag values. We're not that strict: We allow both '*' and ETag values in a list. If the server sends such
+        // an invalid list, we want to be able to represent it using the corresponding header property.
+        private static readonly HttpHeaderParser<EntityTagHeaderValue> MultipleValueParser
+            = new GenericHeaderParser<EntityTagHeaderValue>(true, GetEntityTagLength);
+
+        private static EntityTagHeaderValue AnyType;
+
+        private StringSegment _tag;
+        private bool _isWeak;
+
+        private EntityTagHeaderValue()
+        {
+            // Used by the parser to create a new instance of this type.
+        }
+
+        public EntityTagHeaderValue(StringSegment tag)
+            : this(tag, false)
+        {
+        }
+
+        public EntityTagHeaderValue(StringSegment tag, bool isWeak)
+        {
+            if (StringSegment.IsNullOrEmpty(tag))
+            {
+                throw new ArgumentException("An empty string is not allowed.", nameof(tag));
+            }
+
+            int length = 0;
+            if (!isWeak && StringSegment.Equals(tag, "*", StringComparison.Ordinal))
+            {
+                // * is valid, but W/* isn't.
+                _tag = tag;
+            }
+            else if ((HttpRuleParser.GetQuotedStringLength(tag, 0, out length) != HttpParseResult.Parsed) ||
+                (length != tag.Length))
+            {
+                // Note that we don't allow 'W/' prefixes for weak ETags in the 'tag' parameter. If the user wants to
+                // add a weak ETag, he can set 'isWeak' to true.
+                throw new FormatException("Invalid ETag name");
+            }
+
+            _tag = tag;
+            _isWeak = isWeak;
+        }
+
+        public static EntityTagHeaderValue Any
+        {
+            get
+            {
+                if (AnyType == null)
+                {
+                    AnyType = new EntityTagHeaderValue();
+                    AnyType._tag = "*";
+                    AnyType._isWeak = false;
+                }
+                return AnyType;
+            }
+        }
+
+        public StringSegment Tag
+        {
+            get { return _tag; }
+        }
+
+        public bool IsWeak
+        {
+            get { return _isWeak; }
+        }
+
+        public override string ToString()
+        {
+            if (_isWeak)
+            {
+                return "W/" + _tag.ToString();
+            }
+            return _tag.ToString();
+        }
+
+        /// <summary>
+        /// Check against another <see cref="EntityTagHeaderValue"/> for equality.
+        /// This equality check should not be used to determine if two values match under the RFC specifications (https://tools.ietf.org/html/rfc7232#section-2.3.2).
+        /// </summary>
+        /// <param name="obj">The other value to check against for equality.</param>
+        /// <returns>
+        /// <c>true</c> if the strength and tag of the two values match,
+        /// <c>false</c> if the other value is null, is not an <see cref="EntityTagHeaderValue"/>, or if there is a mismatch of strength or tag between the two values.
+        /// </returns>
+        public override bool Equals(object obj)
+        {
+            var other = obj as EntityTagHeaderValue;
+
+            if (other == null)
+            {
+                return false;
+            }
+
+            // Since the tag is a quoted-string we treat it case-sensitive.
+            return _isWeak == other._isWeak && StringSegment.Equals(_tag, other._tag, StringComparison.Ordinal);
+        }
+
+        public override int GetHashCode()
+        {
+            // Since the tag is a quoted-string we treat it case-sensitive.
+            return _tag.GetHashCode() ^ _isWeak.GetHashCode();
+        }
+
+        /// <summary>
+        /// Compares against another <see cref="EntityTagHeaderValue"/> to see if they match under the RFC specifications (https://tools.ietf.org/html/rfc7232#section-2.3.2).
+        /// </summary>
+        /// <param name="other">The other <see cref="EntityTagHeaderValue"/> to compare against.</param>
+        /// <param name="useStrongComparison"><c>true</c> to use a strong comparison, <c>false</c> to use a weak comparison</param>
+        /// <returns>
+        /// <c>true</c> if the <see cref="EntityTagHeaderValue"/> match for the given comparison type,
+        /// <c>false</c> if the other value is null or the comparison failed.
+        /// </returns>
+        public bool Compare(EntityTagHeaderValue other, bool useStrongComparison)
+        {
+            if (other == null)
+            {
+                return false;
+            }
+
+            if (useStrongComparison)
+            {
+                return !IsWeak && !other.IsWeak && StringSegment.Equals(Tag, other.Tag, StringComparison.Ordinal);
+            }
+            else
+            {
+                return StringSegment.Equals(Tag, other.Tag, StringComparison.Ordinal);
+            }
+        }
+
+        public static EntityTagHeaderValue Parse(StringSegment input)
+        {
+            var index = 0;
+            return SingleValueParser.ParseValue(input, ref index);
+        }
+
+        public static bool TryParse(StringSegment input, out EntityTagHeaderValue parsedValue)
+        {
+            var index = 0;
+            return SingleValueParser.TryParseValue(input, ref index, out parsedValue);
+        }
+
+        public static IList<EntityTagHeaderValue> ParseList(IList<string> inputs)
+        {
+            return MultipleValueParser.ParseValues(inputs);
+        }
+
+        public static IList<EntityTagHeaderValue> ParseStrictList(IList<string> inputs)
+        {
+            return MultipleValueParser.ParseStrictValues(inputs);
+        }
+
+        public static bool TryParseList(IList<string> inputs, out IList<EntityTagHeaderValue> parsedValues)
+        {
+            return MultipleValueParser.TryParseValues(inputs, out parsedValues);
+        }
+
+        public static bool TryParseStrictList(IList<string> inputs, out IList<EntityTagHeaderValue> parsedValues)
+        {
+            return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
+        }
+
+        internal static int GetEntityTagLength(StringSegment input, int startIndex, out EntityTagHeaderValue parsedValue)
+        {
+            Contract.Requires(startIndex >= 0);
+
+            parsedValue = null;
+
+            if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+            {
+                return 0;
+            }
+
+            // Caller must remove leading whitespaces. If not, we'll return 0.
+            var isWeak = false;
+            var current = startIndex;
+
+            var firstChar = input[startIndex];
+            if (firstChar == '*')
+            {
+                // We have '*' value, indicating "any" ETag.
+                parsedValue = Any;
+                current++;
+            }
+            else
+            {
+                // The RFC defines 'W/' as prefix, but we'll be flexible and also accept lower-case 'w'.
+                if ((firstChar == 'W') || (firstChar == 'w'))
+                {
+                    current++;
+                    // We need at least 3 more chars: the '/' character followed by two quotes.
+                    if ((current + 2 >= input.Length) || (input[current] != '/'))
+                    {
+                        return 0;
+                    }
+                    isWeak = true;
+                    current++; // we have a weak-entity tag.
+                    current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+                }
+
+                var tagStartIndex = current;
+                var tagLength = 0;
+                if (HttpRuleParser.GetQuotedStringLength(input, current, out tagLength) != HttpParseResult.Parsed)
+                {
+                    return 0;
+                }
+
+                parsedValue = new EntityTagHeaderValue();
+                if (tagLength == input.Length)
+                {
+                    // Most of the time we'll have strong ETags without leading/trailing whitespaces.
+                    Contract.Assert(startIndex == 0);
+                    Contract.Assert(!isWeak);
+                    parsedValue._tag = input;
+                    parsedValue._isWeak = false;
+                }
+                else
+                {
+                    parsedValue._tag = input.Subsegment(tagStartIndex, tagLength);
+                    parsedValue._isWeak = isWeak;
+                }
+
+                current = current + tagLength;
+            }
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+            return current - startIndex;
+        }
+    }
+}
diff --git a/src/Http/Headers/src/GenericHeaderParser.cs b/src/Http/Headers/src/GenericHeaderParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a2fbf720f9f78d403f855769530e914487b00269
--- /dev/null
+++ b/src/Http/Headers/src/GenericHeaderParser.cs
@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    internal sealed class GenericHeaderParser<T> : BaseHeaderParser<T>
+    {
+        internal delegate int GetParsedValueLengthDelegate(StringSegment value, int startIndex, out T parsedValue);
+
+        private GetParsedValueLengthDelegate _getParsedValueLength;
+
+        internal GenericHeaderParser(bool supportsMultipleValues, GetParsedValueLengthDelegate getParsedValueLength)
+            : base(supportsMultipleValues)
+        {
+            if (getParsedValueLength == null)
+            {
+                throw new ArgumentNullException(nameof(getParsedValueLength));
+            }
+
+            _getParsedValueLength = getParsedValueLength;
+        }
+
+        protected override int GetParsedValueLength(StringSegment value, int startIndex, out T parsedValue)
+        {
+            return _getParsedValueLength(value, startIndex, out parsedValue);
+        }
+    }
+}
diff --git a/src/Http/Headers/src/HeaderNames.cs b/src/Http/Headers/src/HeaderNames.cs
new file mode 100644
index 0000000000000000000000000000000000000000..fe79d242e8a345a4a946dc38ab6ec03334e687f9
--- /dev/null
+++ b/src/Http/Headers/src/HeaderNames.cs
@@ -0,0 +1,77 @@
+// 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.Net.Http.Headers
+{
+    public static class HeaderNames
+    {
+        public const string Accept = "Accept";
+        public const string AcceptCharset = "Accept-Charset";
+        public const string AcceptEncoding = "Accept-Encoding";
+        public const string AcceptLanguage = "Accept-Language";
+        public const string AcceptRanges = "Accept-Ranges";
+        public const string AccessControlAllowCredentials = "Access-Control-Allow-Credentials";
+        public const string AccessControlAllowHeaders = "Access-Control-Allow-Headers";
+        public const string AccessControlAllowMethods = "Access-Control-Allow-Methods";
+        public const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
+        public const string AccessControlExposeHeaders = "Access-Control-Expose-Headers";
+        public const string AccessControlMaxAge = "Access-Control-Max-Age";
+        public const string AccessControlRequestHeaders = "Access-Control-Request-Headers";
+        public const string AccessControlRequestMethod = "Access-Control-Request-Method";
+        public const string Age = "Age";
+        public const string Allow = "Allow";
+        public const string Authority = ":authority";
+        public const string Authorization = "Authorization";
+        public const string CacheControl = "Cache-Control";
+        public const string Connection = "Connection";
+        public const string ContentDisposition = "Content-Disposition";
+        public const string ContentEncoding = "Content-Encoding";
+        public const string ContentLanguage = "Content-Language";
+        public const string ContentLength = "Content-Length";
+        public const string ContentLocation = "Content-Location";
+        public const string ContentMD5 = "Content-MD5";
+        public const string ContentRange = "Content-Range";
+        public const string ContentSecurityPolicy = "Content-Security-Policy";
+        public const string ContentSecurityPolicyReportOnly = "Content-Security-Policy-Report-Only";
+        public const string ContentType = "Content-Type";
+        public const string Cookie = "Cookie";
+        public const string Date = "Date";
+        public const string ETag = "ETag";
+        public const string Expires = "Expires";
+        public const string Expect = "Expect";
+        public const string From = "From";
+        public const string Host = "Host";
+        public const string IfMatch = "If-Match";
+        public const string IfModifiedSince = "If-Modified-Since";
+        public const string IfNoneMatch = "If-None-Match";
+        public const string IfRange = "If-Range";
+        public const string IfUnmodifiedSince = "If-Unmodified-Since";
+        public const string LastModified = "Last-Modified";
+        public const string Location = "Location";
+        public const string MaxForwards = "Max-Forwards";
+        public const string Method = ":method";
+        public const string Origin = "Origin";
+        public const string Path = ":path";
+        public const string Pragma = "Pragma";
+        public const string ProxyAuthenticate = "Proxy-Authenticate";
+        public const string ProxyAuthorization = "Proxy-Authorization";
+        public const string Range = "Range";
+        public const string Referer = "Referer";
+        public const string RetryAfter = "Retry-After";
+        public const string Scheme = ":scheme";
+        public const string Server = "Server";
+        public const string SetCookie = "Set-Cookie";
+        public const string Status = ":status";
+        public const string StrictTransportSecurity = "Strict-Transport-Security";
+        public const string TE = "TE";
+        public const string Trailer = "Trailer";
+        public const string TransferEncoding = "Transfer-Encoding";
+        public const string Upgrade = "Upgrade";
+        public const string UserAgent = "User-Agent";
+        public const string Vary = "Vary";
+        public const string Via = "Via";
+        public const string Warning = "Warning";
+        public const string WebSocketSubProtocols = "Sec-WebSocket-Protocol";
+        public const string WWWAuthenticate = "WWW-Authenticate";
+    }
+}
diff --git a/src/Http/Headers/src/HeaderQuality.cs b/src/Http/Headers/src/HeaderQuality.cs
new file mode 100644
index 0000000000000000000000000000000000000000..da864507265b9aff52daf9cacc60e45924ce8e81
--- /dev/null
+++ b/src/Http/Headers/src/HeaderQuality.cs
@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.Net.Http.Headers
+{
+    public static class HeaderQuality
+    {
+        /// <summary>
+        /// Quality factor to indicate a perfect match.
+        /// </summary>
+        public const double Match = 1.0;
+
+        /// <summary>
+        /// Quality factor to indicate no match.
+        /// </summary>
+        public const double NoMatch = 0.0;
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Headers/src/HeaderUtilities.cs b/src/Http/Headers/src/HeaderUtilities.cs
new file mode 100644
index 0000000000000000000000000000000000000000..20b4319252113e3974019fdaab6b5567020dea32
--- /dev/null
+++ b/src/Http/Headers/src/HeaderUtilities.cs
@@ -0,0 +1,732 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.Contracts;
+using System.Globalization;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public static class HeaderUtilities
+    {
+        private static readonly int _int64MaxStringLength = 19;
+        private static readonly int _qualityValueMaxCharCount = 10; // Little bit more permissive than RFC7231 5.3.1
+        private const string QualityName = "q";
+        internal const string BytesUnit = "bytes";
+
+        internal static void SetQuality(IList<NameValueHeaderValue> parameters, double? value)
+        {
+            Contract.Requires(parameters != null);
+
+            var qualityParameter = NameValueHeaderValue.Find(parameters, QualityName);
+            if (value.HasValue)
+            {
+                // Note that even if we check the value here, we can't prevent a user from adding an invalid quality
+                // value using Parameters.Add(). Even if we would prevent the user from adding an invalid value
+                // using Parameters.Add() he could always add invalid values using HttpHeaders.AddWithoutValidation().
+                // So this check is really for convenience to show users that they're trying to add an invalid
+                // value.
+                if ((value < 0) || (value > 1))
+                {
+                    throw new ArgumentOutOfRangeException(nameof(value));
+                }
+
+                var qualityString = ((double)value).ToString("0.0##", NumberFormatInfo.InvariantInfo);
+                if (qualityParameter != null)
+                {
+                    qualityParameter.Value = qualityString;
+                }
+                else
+                {
+                    parameters.Add(new NameValueHeaderValue(QualityName, qualityString));
+                }
+            }
+            else
+            {
+                // Remove quality parameter
+                if (qualityParameter != null)
+                {
+                    parameters.Remove(qualityParameter);
+                }
+            }
+        }
+
+        internal static double? GetQuality(IList<NameValueHeaderValue> parameters)
+        {
+            Contract.Requires(parameters != null);
+
+            var qualityParameter = NameValueHeaderValue.Find(parameters, QualityName);
+            if (qualityParameter != null)
+            {
+                // Note that the RFC requires decimal '.' regardless of the culture. I.e. using ',' as decimal
+                // separator is considered invalid (even if the current culture would allow it).
+                if (TryParseQualityDouble(qualityParameter.Value, 0, out var qualityValue, out var length))
+
+                {
+                    return qualityValue;
+                }
+            }
+            return null;
+        }
+
+        internal static void CheckValidToken(StringSegment value, string parameterName)
+        {
+            if (StringSegment.IsNullOrEmpty(value))
+            {
+                throw new ArgumentException("An empty string is not allowed.", parameterName);
+            }
+
+            if (HttpRuleParser.GetTokenLength(value, 0) != value.Length)
+            {
+                throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Invalid token '{0}.", value));
+            }
+        }
+
+        internal static bool AreEqualCollections<T>(ICollection<T> x, ICollection<T> y)
+        {
+            return AreEqualCollections(x, y, null);
+        }
+
+        internal static bool AreEqualCollections<T>(ICollection<T> x, ICollection<T> y, IEqualityComparer<T> comparer)
+        {
+            if (x == null)
+            {
+                return (y == null) || (y.Count == 0);
+            }
+
+            if (y == null)
+            {
+                return (x.Count == 0);
+            }
+
+            if (x.Count != y.Count)
+            {
+                return false;
+            }
+
+            if (x.Count == 0)
+            {
+                return true;
+            }
+
+            // We have two unordered lists. So comparison is an O(n*m) operation which is expensive. Usually
+            // headers have 1-2 parameters (if any), so this comparison shouldn't be too expensive.
+            var alreadyFound = new bool[x.Count];
+            var i = 0;
+            foreach (var xItem in x)
+            {
+                Contract.Assert(xItem != null);
+
+                i = 0;
+                var found = false;
+                foreach (var yItem in y)
+                {
+                    if (!alreadyFound[i])
+                    {
+                        if (((comparer == null) && xItem.Equals(yItem)) ||
+                            ((comparer != null) && comparer.Equals(xItem, yItem)))
+                        {
+                            alreadyFound[i] = true;
+                            found = true;
+                            break;
+                        }
+                    }
+                    i++;
+                }
+
+                if (!found)
+                {
+                    return false;
+                }
+            }
+
+            // Since we never re-use a "found" value in 'y', we expecte 'alreadyFound' to have all fields set to 'true'.
+            // Otherwise the two collections can't be equal and we should not get here.
+            Contract.Assert(Contract.ForAll(alreadyFound, value => { return value; }),
+                "Expected all values in 'alreadyFound' to be true since collections are considered equal.");
+
+            return true;
+        }
+
+        internal static int GetNextNonEmptyOrWhitespaceIndex(
+            StringSegment input,
+            int startIndex,
+            bool skipEmptyValues,
+            out bool separatorFound)
+        {
+            Contract.Requires(input != null);
+            Contract.Requires(startIndex <= input.Length); // it's OK if index == value.Length.
+
+            separatorFound = false;
+            var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex);
+
+            if ((current == input.Length) || (input[current] != ','))
+            {
+                return current;
+            }
+
+            // If we have a separator, skip the separator and all following whitespaces. If we support
+            // empty values, continue until the current character is neither a separator nor a whitespace.
+            separatorFound = true;
+            current++; // skip delimiter.
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+            if (skipEmptyValues)
+            {
+                while ((current < input.Length) && (input[current] == ','))
+                {
+                    current++; // skip delimiter.
+                    current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+                }
+            }
+
+            return current;
+        }
+
+        private static int AdvanceCacheDirectiveIndex(int current, string headerValue)
+        {
+            // Skip until the next potential name
+            current += HttpRuleParser.GetWhitespaceLength(headerValue, current);
+
+            // Skip the value if present
+            if (current < headerValue.Length && headerValue[current] == '=')
+            {
+                current++; // skip '='
+                current += NameValueHeaderValue.GetValueLength(headerValue, current);
+            }
+
+            // Find the next delimiter
+            current = headerValue.IndexOf(',', current);
+
+            if (current == -1)
+            {
+                // If no delimiter found, skip to the end
+                return headerValue.Length;
+            }
+
+            current++; // skip ','
+            current += HttpRuleParser.GetWhitespaceLength(headerValue, current);
+
+            return current;
+        }
+
+        /// <summary>
+        /// Try to find a target header value among the set of given header values and parse it as a
+        /// <see cref="TimeSpan"/>.
+        /// </summary>
+        /// <param name="headerValues">
+        /// The <see cref="StringValues"/> containing the set of header values to search.
+        /// </param>
+        /// <param name="targetValue">
+        /// The target header value to look for.
+        /// </param>
+        /// <param name="value">
+        /// When this method returns, contains the parsed <see cref="TimeSpan"/>, if the parsing succeeded, or
+        /// null if the parsing failed. The conversion fails if the <paramref name="targetValue"/> was not
+        /// found or could not be parsed as a <see cref="TimeSpan"/>. This parameter is passed uninitialized;
+        /// any value originally supplied in result will be overwritten.
+        /// </param>
+        /// <returns>
+        /// <code>true</code> if <paramref name="targetValue"/> is found and successfully parsed; otherwise,
+        /// <code>false</code>.
+        /// </returns>
+        // e.g. { "headerValue=10, targetHeaderValue=30" }
+        public static bool TryParseSeconds(StringValues headerValues, string targetValue, out TimeSpan? value)
+        {
+            if (StringValues.IsNullOrEmpty(headerValues) || string.IsNullOrEmpty(targetValue))
+            {
+                value = null;
+                return false;
+            }
+
+            for (var i = 0; i < headerValues.Count; i++)
+            {
+                // Trim leading white space
+                var current = HttpRuleParser.GetWhitespaceLength(headerValues[i], 0);
+
+                while (current < headerValues[i].Length)
+                {
+                    long seconds;
+                    var initial = current;
+                    var tokenLength = HttpRuleParser.GetTokenLength(headerValues[i], current);
+                    if (tokenLength == targetValue.Length
+                        && string.Compare(headerValues[i], current, targetValue, 0, tokenLength, StringComparison.OrdinalIgnoreCase) == 0
+                        && TryParseNonNegativeInt64FromHeaderValue(current + tokenLength, headerValues[i], out seconds))
+                    {
+                        // Token matches target value and seconds were parsed
+                        value = TimeSpan.FromSeconds(seconds);
+                        return true;
+                    }
+
+                    current = AdvanceCacheDirectiveIndex(current + tokenLength, headerValues[i]);
+
+                    // Ensure index was advanced
+                    if (current <= initial)
+                    {
+                        Debug.Assert(false, $"Index '{nameof(current)}' not advanced, this is a bug.");
+                        value = null;
+                        return false;
+                    }
+                }
+            }
+            value = null;
+            return false;
+        }
+
+        /// <summary>
+        /// Check if a target directive exists among the set of given cache control directives.
+        /// </summary>
+        /// <param name="cacheControlDirectives">
+        /// The <see cref="StringValues"/> containing the set of cache control directives.
+        /// </param>
+        /// <param name="targetDirectives">
+        /// The target cache control directives to look for.
+        /// </param>
+        /// <returns>
+        /// <code>true</code> if <paramref name="targetDirectives"/> is contained in <paramref name="cacheControlDirectives"/>;
+        /// otherwise, <code>false</code>.
+        /// </returns>
+        public static bool ContainsCacheDirective(StringValues cacheControlDirectives, string targetDirectives)
+        {
+            if (StringValues.IsNullOrEmpty(cacheControlDirectives) || string.IsNullOrEmpty(targetDirectives))
+            {
+                return false;
+            }
+
+            for (var i = 0; i < cacheControlDirectives.Count; i++)
+            {
+                // Trim leading white space
+                var current = HttpRuleParser.GetWhitespaceLength(cacheControlDirectives[i], 0);
+
+                while (current < cacheControlDirectives[i].Length)
+                {
+                    var initial = current;
+
+                    var tokenLength = HttpRuleParser.GetTokenLength(cacheControlDirectives[i], current);
+                    if (tokenLength == targetDirectives.Length
+                        && string.Compare(cacheControlDirectives[i], current, targetDirectives, 0, tokenLength, StringComparison.OrdinalIgnoreCase) == 0)
+                    {
+                        // Token matches target value
+                        return true;
+                    }
+
+                    current = AdvanceCacheDirectiveIndex(current + tokenLength, cacheControlDirectives[i]);
+
+                    // Ensure index was advanced
+                    if (current <= initial)
+                    {
+                        Debug.Assert(false, $"Index '{nameof(current)}' not advanced, this is a bug.");
+                        return false;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        private static unsafe bool TryParseNonNegativeInt64FromHeaderValue(int startIndex, string headerValue, out long result)
+        {
+            // Trim leading whitespace
+            startIndex += HttpRuleParser.GetWhitespaceLength(headerValue, startIndex);
+
+            // Match and skip '=', it also can't be the last character in the headerValue
+            if (startIndex >= headerValue.Length - 1 || headerValue[startIndex] != '=')
+            {
+                result = 0;
+                return false;
+            }
+            startIndex++;
+
+            // Trim trailing whitespace
+            startIndex += HttpRuleParser.GetWhitespaceLength(headerValue, startIndex);
+
+            // Try parse the number
+            if (TryParseNonNegativeInt64(new StringSegment(headerValue, startIndex, HttpRuleParser.GetNumberLength(headerValue, startIndex, false)), out result))
+            {
+                return true;
+            }
+
+            result = 0;
+            return false;
+        }
+
+        /// <summary>
+        /// Try to convert a string representation of a positive number to its 64-bit signed integer equivalent.
+        /// A return value indicates whether the conversion succeeded or failed.
+        /// </summary>
+        /// <param name="value">
+        /// A string containing a number to convert.
+        /// </param>
+        /// <param name="result">
+        /// When this method returns, contains the 64-bit signed integer value equivalent of the number contained
+        /// in the string, if the conversion succeeded, or zero if the conversion failed. The conversion fails if
+        /// the string is null or String.Empty, is not of the correct format, is negative, or represents a number
+        /// greater than Int64.MaxValue. This parameter is passed uninitialized; any value originally supplied in
+        /// result will be overwritten.
+        /// </param>
+        /// <returns><code>true</code> if parsing succeeded; otherwise, <code>false</code>.</returns>
+        public static unsafe bool TryParseNonNegativeInt32(StringSegment value, out int result)
+        {
+            if (string.IsNullOrEmpty(value.Buffer) || value.Length == 0)
+            {
+                result = 0;
+                return false;
+            }
+
+            result = 0;
+            fixed (char* ptr = value.Buffer)
+            {
+                var ch = (ushort*)ptr + value.Offset;
+                var end = ch + value.Length;
+
+                ushort digit = 0;
+                while (ch < end && (digit = (ushort)(*ch - 0x30)) <= 9)
+                {
+                    // Check for overflow
+                    if ((result = result * 10 + digit) < 0)
+                    {
+                        result = 0;
+                        return false;
+                    }
+
+                    ch++;
+                }
+
+                if (ch != end)
+                {
+                    result = 0;
+                    return false;
+                }
+                return true;
+            }
+        }
+
+        /// <summary>
+        /// Try to convert a <see cref="StringSegment"/> representation of a positive number to its 64-bit signed
+        /// integer equivalent. A return value indicates whether the conversion succeeded or failed.
+        /// </summary>
+        /// <param name="value">
+        /// A <see cref="StringSegment"/> containing a number to convert.
+        /// </param>
+        /// <param name="result">
+        /// When this method returns, contains the 64-bit signed integer value equivalent of the number contained
+        /// in the string, if the conversion succeeded, or zero if the conversion failed. The conversion fails if
+        /// the <see cref="StringSegment"/> is null or String.Empty, is not of the correct format, is negative, or
+        /// represents a number greater than Int64.MaxValue. This parameter is passed uninitialized; any value
+        /// originally supplied in result will be overwritten.
+        /// </param>
+        /// <returns><code>true</code> if parsing succeeded; otherwise, <code>false</code>.</returns>
+        public static unsafe bool TryParseNonNegativeInt64(StringSegment value, out long result)
+        {
+            if (string.IsNullOrEmpty(value.Buffer) || value.Length == 0)
+            {
+                result = 0;
+                return false;
+            }
+
+            result = 0;
+            fixed (char* ptr = value.Buffer)
+            {
+                var ch = (ushort*)ptr + value.Offset;
+                var end = ch + value.Length;
+
+                ushort digit = 0;
+                while (ch < end && (digit = (ushort)(*ch - 0x30)) <= 9)
+                {
+                    // Check for overflow
+                    if ((result = result * 10 + digit) < 0)
+                    {
+                        result = 0;
+                        return false;
+                    }
+
+                    ch++;
+                }
+
+                if (ch != end)
+                {
+                    result = 0;
+                    return false;
+                }
+                return true;
+            }
+        }
+
+        // Strict and fast RFC7231 5.3.1 Quality value parser (and without memory allocation)
+        // See https://tools.ietf.org/html/rfc7231#section-5.3.1
+        // Check is made to verify if the value is between 0 and 1 (and it returns False if the check fails).
+        internal static bool TryParseQualityDouble(StringSegment input, int startIndex, out double quality, out int length)
+        {
+            quality = 0;
+            length = 0;
+
+            var inputLength = input.Length;
+            var current = startIndex;
+            var limit = startIndex + _qualityValueMaxCharCount;
+
+            var intPart = 0;
+            var decPart = 0;
+            var decPow = 1;
+
+            if (current >= inputLength)
+            {
+                return false;
+            }
+
+            var ch = input[current];
+
+            if (ch >= '0' && ch <= '1') // Only values between 0 and 1 are accepted, according to RFC
+            {
+                intPart = ch - '0';
+                current++;
+            }
+            else
+            {
+                // The RFC doesn't allow decimal values starting with dot. I.e. value ".123" is invalid. It must be in the
+                // form "0.123".
+                return false;
+            }
+
+            if (current < inputLength)
+            {
+                ch = input[current];
+
+                if (ch >= '0' && ch <= '9')
+                {
+                    // The RFC accepts only one digit before the dot
+                    return false;
+                }
+
+                if (ch == '.')
+                {
+                    current++;
+
+                    while (current < inputLength)
+                    {
+                        ch = input[current];
+                        if (ch >= '0' && ch <= '9')
+                        {
+                            if (current >= limit)
+                            {
+                                return false;
+                            }
+
+                            decPart = decPart * 10 + ch - '0';
+                            decPow *= 10;
+                            current++;
+                        }
+                        else
+                        {
+                            break;
+                        }
+                    }
+                }
+            }
+
+            if (decPart != 0)
+            {
+                quality = intPart + decPart / (double)decPow;
+            }
+            else
+            {
+                quality = intPart;
+            }
+
+            if (quality > 1)
+            {
+                // reset quality
+                quality = 0;
+                return false;
+            }
+
+            length = current - startIndex;
+            return true;
+        }
+
+        /// <summary>
+        /// Converts the non-negative 64-bit numeric value to its equivalent string representation.
+        /// </summary>
+        /// <param name="value">
+        /// The number to convert.
+        /// </param>
+        /// <returns>
+        /// The string representation of the value of this instance, consisting of a sequence of digits ranging from 0 to 9 with no leading zeroes.
+        /// </returns>
+        public unsafe static string FormatNonNegativeInt64(long value)
+        {
+            if (value < 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(value), value, "The value to be formatted must be non-negative.");
+            }
+
+            var position = _int64MaxStringLength;
+            char* charBuffer = stackalloc char[_int64MaxStringLength];
+
+            do
+            {
+                // Consider using Math.DivRem() if available
+                var quotient = value / 10;
+                charBuffer[--position] = (char)(0x30 + (value - quotient * 10)); // 0x30 = '0'
+                value = quotient;
+            }
+            while (value != 0);
+
+            return new string(charBuffer, position, _int64MaxStringLength - position);
+        }
+
+        public static bool TryParseDate(StringSegment input, out DateTimeOffset result)
+        {
+            return HttpRuleParser.TryStringToDate(input, out result);
+        }
+
+        public static string FormatDate(DateTimeOffset dateTime)
+        {
+            return FormatDate(dateTime, false);
+        }
+
+        public static string FormatDate(DateTimeOffset dateTime, bool quoted)
+        {
+            return dateTime.ToRfc1123String(quoted);
+        }
+
+        public static StringSegment RemoveQuotes(StringSegment input)
+        {
+            if (IsQuoted(input))
+            {
+                input = input.Subsegment(1, input.Length - 2);
+            }
+            return input;
+        }
+
+        public static bool IsQuoted(StringSegment input)
+        {
+            return !StringSegment.IsNullOrEmpty(input) && input.Length >= 2 && input[0] == '"' && input[input.Length - 1] == '"';
+        }
+
+        /// <summary>
+        /// Given a quoted-string as defined by <see href="https://tools.ietf.org/html/rfc7230#section-3.2.6">the RFC specification</see>,
+        /// removes quotes and unescapes backslashes and quotes. This assumes that the input is a valid quoted-string.
+        /// </summary>
+        /// <param name="input">The quoted-string to be unescaped.</param>
+        /// <returns>An unescaped version of the quoted-string.</returns>
+        public static StringSegment UnescapeAsQuotedString(StringSegment input)
+        {
+            input = RemoveQuotes(input);
+
+            // First pass to calculate the size of the InplaceStringBuilder
+            var backSlashCount = CountBackslashesForDecodingQuotedString(input);
+
+            if (backSlashCount == 0)
+            {
+                return input;
+            }
+
+            var stringBuilder = new InplaceStringBuilder(input.Length - backSlashCount);
+
+            for (var i = 0; i < input.Length; i++)
+            {
+                if (i < input.Length - 1 && input[i] == '\\')
+                {
+                    // If there is an backslash character as the last character in the string,
+                    // we will assume that it should be included literally in the unescaped string
+                    // Ex: "hello\\" => "hello\\"
+                    // Also, if a sender adds a quoted pair like '\\''n',
+                    // we will assume it is over escaping and just add a n to the string.
+                    // Ex: "he\\llo" => "hello"
+                    stringBuilder.Append(input[i + 1]);
+                    i++;
+                    continue;
+                }
+                stringBuilder.Append(input[i]);
+            }
+
+            return stringBuilder.ToString();
+        }
+
+        private static int CountBackslashesForDecodingQuotedString(StringSegment input)
+        {
+            var numberBackSlashes = 0;
+            for (var i = 0; i < input.Length; i++)
+            {
+                if (i < input.Length - 1 && input[i] == '\\')
+                {
+                    // If there is an backslash character as the last character in the string,
+                    // we will assume that it should be included literally in the unescaped string
+                    // Ex: "hello\\" => "hello\\"
+                    // Also, if a sender adds a quoted pair like '\\''n',
+                    // we will assume it is over escaping and just add a n to the string.
+                    // Ex: "he\\llo" => "hello"
+                    if (input[i + 1] == '\\')
+                    {
+                        // Only count escaped backslashes once
+                        i++;
+                    }
+                    numberBackSlashes++;
+                }
+            }
+            return numberBackSlashes;
+        }
+
+        /// <summary>
+        /// Escapes a <see cref="StringSegment"/> as a quoted-string, which is defined by
+        /// <see href="https://tools.ietf.org/html/rfc7230#section-3.2.6">the RFC specification</see>.
+        /// </summary>
+        /// <remarks>
+        /// This will add a backslash before each backslash and quote and add quotes
+        /// around the input. Assumes that the input does not have quotes around it,
+        /// as this method will add them. Throws if the input contains any invalid escape characters,
+        /// as defined by rfc7230.
+        /// </remarks>
+        /// <param name="input">The input to be escaped.</param>
+        /// <returns>An escaped version of the quoted-string.</returns>
+        public static StringSegment EscapeAsQuotedString(StringSegment input)
+        {
+            // By calling this, we know that the string requires quotes around it to be a valid token.
+            var backSlashCount = CountAndCheckCharactersNeedingBackslashesWhenEncoding(input);
+
+            var stringBuilder = new InplaceStringBuilder(input.Length + backSlashCount + 2); // 2 for quotes
+            stringBuilder.Append('\"');
+
+            for (var i = 0; i < input.Length; i++)
+            {
+                if (input[i] == '\\' || input[i] == '\"')
+                {
+                    stringBuilder.Append('\\');
+                }
+                else if ((input[i] <= 0x1F || input[i] == 0x7F) && input[i] != 0x09)
+                {
+                    // Control characters are not allowed in a quoted-string, which include all characters
+                    // below 0x1F (except for 0x09 (TAB)) and 0x7F.
+                    throw new FormatException($"Invalid control character '{input[i]}' in input.");
+                }
+                stringBuilder.Append(input[i]);
+            }
+            stringBuilder.Append('\"');
+            return stringBuilder.ToString();
+        }
+
+        private static int CountAndCheckCharactersNeedingBackslashesWhenEncoding(StringSegment input)
+        {
+            var numberOfCharactersNeedingEscaping = 0;
+            for (var i = 0; i < input.Length; i++)
+            {
+                if (input[i] == '\\' || input[i] == '\"')
+                {
+                    numberOfCharactersNeedingEscaping++;
+                }
+            }
+            return numberOfCharactersNeedingEscaping;
+        }
+
+        internal static void ThrowIfReadOnly(bool isReadOnly)
+        {
+            if (isReadOnly)
+            {
+                throw new InvalidOperationException("The object cannot be modified because it is read-only.");
+            }
+        }
+    }
+}
diff --git a/src/Http/Headers/src/HttpHeaderParser.cs b/src/Http/Headers/src/HttpHeaderParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..027a9de43840378bbe375427e7396cd95de080e0
--- /dev/null
+++ b/src/Http/Headers/src/HttpHeaderParser.cs
@@ -0,0 +1,172 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Globalization;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    internal abstract class HttpHeaderParser<T>
+    {
+        private bool _supportsMultipleValues;
+
+        protected HttpHeaderParser(bool supportsMultipleValues)
+        {
+            _supportsMultipleValues = supportsMultipleValues;
+        }
+
+        public bool SupportsMultipleValues
+        {
+            get { return _supportsMultipleValues; }
+        }
+
+        // If a parser supports multiple values, a call to ParseValue/TryParseValue should return a value for 'index'
+        // pointing to the next non-whitespace character after a delimiter. E.g. if called with a start index of 0
+        // for string "value , second_value", then after the call completes, 'index' must point to 's', i.e. the first
+        // non-whitespace after the separator ','.
+        public abstract bool TryParseValue(StringSegment value, ref int index, out T parsedValue);
+
+        public T ParseValue(StringSegment value, ref int index)
+        {
+            // Index may be value.Length (e.g. both 0). This may be allowed for some headers (e.g. Accept but not
+            // allowed by others (e.g. Content-Length). The parser has to decide if this is valid or not.
+            Contract.Requires((value == null) || ((index >= 0) && (index <= value.Length)));
+
+            // If a parser returns 'null', it means there was no value, but that's valid (e.g. "Accept: "). The caller
+            // can ignore the value.
+            T result;
+            if (!TryParseValue(value, ref index, out result))
+            {
+                throw new FormatException(string.Format(CultureInfo.InvariantCulture,
+                    "The header contains invalid values at index {0}: '{1}'", index, value.Value ?? "<null>"));
+            }
+            return result;
+        }
+
+        public virtual bool TryParseValues(IList<string> values, out IList<T> parsedValues)
+        {
+            return TryParseValues(values, strict: false, parsedValues: out parsedValues);
+        }
+
+        public virtual bool TryParseStrictValues(IList<string> values, out IList<T> parsedValues)
+        {
+            return TryParseValues(values, strict: true, parsedValues: out parsedValues);
+        }
+
+        protected virtual bool TryParseValues(IList<string> values, bool strict, out IList<T> parsedValues)
+        {
+            Contract.Assert(_supportsMultipleValues);
+            // If a parser returns an empty list, it means there was no value, but that's valid (e.g. "Accept: "). The caller
+            // can ignore the value.
+            parsedValues = null;
+            List<T> results = null;
+            if (values == null)
+            {
+                return false;
+            }
+            for (var i = 0; i < values.Count; i++)
+            {
+                var value = values[i];
+                int index = 0;
+
+                while (!string.IsNullOrEmpty(value) && index < value.Length)
+                {
+                    T output;
+                    if (TryParseValue(value, ref index, out output))
+                    {
+                        // The entry may not contain an actual value, like " , "
+                        if (output != null)
+                        {
+                            if (results == null)
+                            {
+                                results = new List<T>();    // Allocate it only when used 
+                            }
+                            results.Add(output);
+                        }
+                    }
+                    else if (strict)
+                    {
+                        return false;
+                    }
+                    else
+                    {
+                        // Skip the invalid values and keep trying.
+                        index++;
+                    }
+                }
+            }
+            if (results != null)
+            {
+                parsedValues = results;
+                return true;
+            }
+            return false;
+        }
+
+        public virtual IList<T> ParseValues(IList<string> values)
+        {
+            return ParseValues(values, strict: false);
+        }
+
+        public virtual IList<T> ParseStrictValues(IList<string> values)
+        {
+            return ParseValues(values, strict: true);
+        }
+
+        protected virtual IList<T> ParseValues(IList<string> values, bool strict)
+        {
+            Contract.Assert(_supportsMultipleValues);
+            // If a parser returns an empty list, it means there was no value, but that's valid (e.g. "Accept: "). The caller
+            // can ignore the value.
+            var parsedValues = new List<T>();
+            if (values == null)
+            {
+                return parsedValues;
+            }
+            foreach (var value in values)
+            {
+                int index = 0;
+
+                while (!string.IsNullOrEmpty(value) && index < value.Length)
+                {
+                    T output;
+                    if (TryParseValue(value, ref index, out output))
+                    {
+                        // The entry may not contain an actual value, like " , "
+                        if (output != null)
+                        {
+                            parsedValues.Add(output);
+                        }
+                    }
+                    else if (strict)
+                    {
+                        throw new FormatException(string.Format(CultureInfo.InvariantCulture,
+                            "The header contains invalid values at index {0}: '{1}'", index, value));
+                    }
+                    else
+                    {
+                        // Skip the invalid values and keep trying.
+                        index++;
+                    }
+                }
+            }
+            return parsedValues;
+        }
+
+        // If ValueType is a custom header value type (e.g. NameValueHeaderValue) it implements ToString() correctly.
+        // However for existing types like int, byte[], DateTimeOffset we can't override ToString(). Therefore the
+        // parser provides a ToString() virtual method that can be overridden by derived types to correctly serialize
+        // values (e.g. byte[] to Base64 encoded string).
+        // The default implementation is to just call ToString() on the value itself which is the right thing to do
+        // for most headers (custom types, string, etc.).
+        public virtual string ToString(object value)
+        {
+            Contract.Requires(value != null);
+
+            return value.ToString();
+        }
+    }
+}
diff --git a/src/Http/Headers/src/HttpParseResult.cs b/src/Http/Headers/src/HttpParseResult.cs
new file mode 100644
index 0000000000000000000000000000000000000000..709ae0ce8434e0ce851418b4fb86fecc3faba8d9
--- /dev/null
+++ b/src/Http/Headers/src/HttpParseResult.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.Net.Http.Headers
+{
+    internal enum HttpParseResult
+    {
+        Parsed,
+        NotParsed,
+        InvalidFormat,
+    }
+}
diff --git a/src/Http/Headers/src/HttpRuleParser.cs b/src/Http/Headers/src/HttpRuleParser.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3741ffa110d90af23f4245afe06e74248d5f9edf
--- /dev/null
+++ b/src/Http/Headers/src/HttpRuleParser.cs
@@ -0,0 +1,349 @@
+// 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.Contracts;
+using System.Globalization;
+using System.Text;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    internal static class HttpRuleParser
+    {
+        private static readonly bool[] TokenChars = CreateTokenChars();
+        private const int MaxNestedCount = 5;
+        private static readonly string[] DateFormats = new string[] {
+            // "r", // RFC 1123, required output format but too strict for input
+            "ddd, d MMM yyyy H:m:s 'GMT'", // RFC 1123 (r, except it allows both 1 and 01 for date and time)
+            "ddd, d MMM yyyy H:m:s", // RFC 1123, no zone - assume GMT
+            "d MMM yyyy H:m:s 'GMT'", // RFC 1123, no day-of-week
+            "d MMM yyyy H:m:s", // RFC 1123, no day-of-week, no zone
+            "ddd, d MMM yy H:m:s 'GMT'", // RFC 1123, short year
+            "ddd, d MMM yy H:m:s", // RFC 1123, short year, no zone
+            "d MMM yy H:m:s 'GMT'", // RFC 1123, no day-of-week, short year
+            "d MMM yy H:m:s", // RFC 1123, no day-of-week, short year, no zone
+
+            "dddd, d'-'MMM'-'yy H:m:s 'GMT'", // RFC 850, short year
+            "dddd, d'-'MMM'-'yy H:m:s", // RFC 850 no zone
+            "ddd, d'-'MMM'-'yyyy H:m:s 'GMT'", // RFC 850, long year
+            "ddd MMM d H:m:s yyyy", // ANSI C's asctime() format
+
+            "ddd, d MMM yyyy H:m:s zzz", // RFC 5322
+            "ddd, d MMM yyyy H:m:s", // RFC 5322 no zone
+            "d MMM yyyy H:m:s zzz", // RFC 5322 no day-of-week
+            "d MMM yyyy H:m:s", // RFC 5322 no day-of-week, no zone
+        };
+
+        internal const char CR = '\r';
+        internal const char LF = '\n';
+        internal const char SP = ' ';
+        internal const char Tab = '\t';
+        internal const int MaxInt64Digits = 19;
+        internal const int MaxInt32Digits = 10;
+
+        // iso-8859-1, Western European (ISO)
+        internal static readonly Encoding DefaultHttpEncoding = Encoding.GetEncoding("iso-8859-1");
+
+        private static bool[] CreateTokenChars()
+        {
+            // token = 1*<any CHAR except CTLs or separators>
+            // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
+
+            var tokenChars = new bool[128]; // everything is false
+
+            for (int i = 33; i < 127; i++) // skip Space (32) & DEL (127)
+            {
+                tokenChars[i] = true;
+            }
+
+            // remove separators: these are not valid token characters
+            tokenChars[(byte)'('] = false;
+            tokenChars[(byte)')'] = false;
+            tokenChars[(byte)'<'] = false;
+            tokenChars[(byte)'>'] = false;
+            tokenChars[(byte)'@'] = false;
+            tokenChars[(byte)','] = false;
+            tokenChars[(byte)';'] = false;
+            tokenChars[(byte)':'] = false;
+            tokenChars[(byte)'\\'] = false;
+            tokenChars[(byte)'"'] = false;
+            tokenChars[(byte)'/'] = false;
+            tokenChars[(byte)'['] = false;
+            tokenChars[(byte)']'] = false;
+            tokenChars[(byte)'?'] = false;
+            tokenChars[(byte)'='] = false;
+            tokenChars[(byte)'{'] = false;
+            tokenChars[(byte)'}'] = false;
+
+            return tokenChars;
+        }
+
+        internal static bool IsTokenChar(char character)
+        {
+            // Must be between 'space' (32) and 'DEL' (127)
+            if (character > 127)
+            {
+                return false;
+            }
+
+            return TokenChars[character];
+        }
+
+        [Pure]
+        internal static int GetTokenLength(StringSegment input, int startIndex)
+        {
+            Contract.Requires(input != null);
+            Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - startIndex)));
+
+            if (startIndex >= input.Length)
+            {
+                return 0;
+            }
+
+            var current = startIndex;
+
+            while (current < input.Length)
+            {
+                if (!IsTokenChar(input[current]))
+                {
+                    return current - startIndex;
+                }
+                current++;
+            }
+            return input.Length - startIndex;
+        }
+
+        internal static int GetWhitespaceLength(StringSegment input, int startIndex)
+        {
+            Contract.Requires(input != null);
+            Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - startIndex)));
+
+            if (startIndex >= input.Length)
+            {
+                return 0;
+            }
+
+            var current = startIndex;
+
+            char c;
+            while (current < input.Length)
+            {
+                c = input[current];
+
+                if ((c == SP) || (c == Tab))
+                {
+                    current++;
+                    continue;
+                }
+
+                if (c == CR)
+                {
+                    // If we have a #13 char, it must be followed by #10 and then at least one SP or HT.
+                    if ((current + 2 < input.Length) && (input[current + 1] == LF))
+                    {
+                        char spaceOrTab = input[current + 2];
+                        if ((spaceOrTab == SP) || (spaceOrTab == Tab))
+                        {
+                            current += 3;
+                            continue;
+                        }
+                    }
+                }
+
+                return current - startIndex;
+            }
+
+            // All characters between startIndex and the end of the string are LWS characters.
+            return input.Length - startIndex;
+        }
+
+        internal static int GetNumberLength(StringSegment input, int startIndex, bool allowDecimal)
+        {
+            Contract.Requires(input != null);
+            Contract.Requires((startIndex >= 0) && (startIndex < input.Length));
+            Contract.Ensures((Contract.Result<int>() >= 0) && (Contract.Result<int>() <= (input.Length - startIndex)));
+
+            var current = startIndex;
+            char c;
+
+            // If decimal values are not allowed, we pretend to have read the '.' character already. I.e. if a dot is
+            // found in the string, parsing will be aborted.
+            var haveDot = !allowDecimal;
+
+            // The RFC doesn't allow decimal values starting with dot. I.e. value ".123" is invalid. It must be in the
+            // form "0.123". Also, there are no negative values defined in the RFC. So we'll just parse non-negative
+            // values.
+            // The RFC only allows decimal dots not ',' characters as decimal separators. Therefore value "1,23" is
+            // considered invalid and must be represented as "1.23".
+            if (input[current] == '.')
+            {
+                return 0;
+            }
+
+            while (current < input.Length)
+            {
+                c = input[current];
+                if ((c >= '0') && (c <= '9'))
+                {
+                    current++;
+                }
+                else if (!haveDot && (c == '.'))
+                {
+                    // Note that value "1." is valid.
+                    haveDot = true;
+                    current++;
+                }
+                else
+                {
+                    break;
+                }
+            }
+
+            return current - startIndex;
+        }
+
+        internal static HttpParseResult GetQuotedStringLength(StringSegment input, int startIndex, out int length)
+        {
+            var nestedCount = 0;
+            return GetExpressionLength(input, startIndex, '"', '"', false, ref nestedCount, out length);
+        }
+
+        // quoted-pair = "\" CHAR
+        // CHAR = <any US-ASCII character (octets 0 - 127)>
+        internal static HttpParseResult GetQuotedPairLength(StringSegment input, int startIndex, out int length)
+        {
+            Contract.Requires(input != null);
+            Contract.Requires((startIndex >= 0) && (startIndex < input.Length));
+            Contract.Ensures((Contract.ValueAtReturn(out length) >= 0) &&
+                (Contract.ValueAtReturn(out length) <= (input.Length - startIndex)));
+
+            length = 0;
+
+            if (input[startIndex] != '\\')
+            {
+                return HttpParseResult.NotParsed;
+            }
+
+            // Quoted-char has 2 characters. Check wheter there are 2 chars left ('\' + char)
+            // If so, check whether the character is in the range 0-127. If not, it's an invalid value.
+            if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127))
+            {
+                return HttpParseResult.InvalidFormat;
+            }
+
+            // We don't care what the char next to '\' is.
+            length = 2;
+            return HttpParseResult.Parsed;
+        }
+
+        // Try the various date formats in the order listed above.
+        // We should accept a wide verity of common formats, but only output RFC 1123 style dates.
+        internal static bool TryStringToDate(StringSegment input, out DateTimeOffset result) =>
+            DateTimeOffset.TryParseExact(input.ToString(), DateFormats, DateTimeFormatInfo.InvariantInfo,
+                DateTimeStyles.AllowWhiteSpaces | DateTimeStyles.AssumeUniversal, out result);
+
+        // TEXT = <any OCTET except CTLs, but including LWS>
+        // LWS = [CRLF] 1*( SP | HT )
+        // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
+        //
+        // Since we don't really care about the content of a quoted string or comment, we're more tolerant and
+        // allow these characters. We only want to find the delimiters ('"' for quoted string and '(', ')' for comment).
+        //
+        // 'nestedCount': Comments can be nested. We allow a depth of up to 5 nested comments, i.e. something like
+        // "(((((comment)))))". If we wouldn't define a limit an attacker could send a comment with hundreds of nested
+        // comments, resulting in a stack overflow exception. In addition having more than 1 nested comment (if any)
+        // is unusual.
+        private static HttpParseResult GetExpressionLength(
+            StringSegment input,
+            int startIndex,
+            char openChar,
+            char closeChar,
+            bool supportsNesting,
+            ref int nestedCount,
+            out int length)
+        {
+            Contract.Requires(input != null);
+            Contract.Requires((startIndex >= 0) && (startIndex < input.Length));
+            Contract.Ensures((Contract.Result<HttpParseResult>() != HttpParseResult.Parsed) ||
+                (Contract.ValueAtReturn<int>(out length) > 0));
+
+            length = 0;
+
+            if (input[startIndex] != openChar)
+            {
+                return HttpParseResult.NotParsed;
+            }
+
+            var current = startIndex + 1; // Start parsing with the character next to the first open-char
+            while (current < input.Length)
+            {
+                // Only check whether we have a quoted char, if we have at least 3 characters left to read (i.e.
+                // quoted char + closing char). Otherwise the closing char may be considered part of the quoted char.
+                var quotedPairLength = 0;
+                if ((current + 2 < input.Length) &&
+                    (GetQuotedPairLength(input, current, out quotedPairLength) == HttpParseResult.Parsed))
+                {
+                    // We ignore invalid quoted-pairs. Invalid quoted-pairs may mean that it looked like a quoted pair,
+                    // but we actually have a quoted-string: e.g. "\ü" ('\' followed by a char >127 - quoted-pair only
+                    // allows ASCII chars after '\'; qdtext allows both '\' and >127 chars).
+                    current = current + quotedPairLength;
+                    continue;
+                }
+
+                // If we support nested expressions and we find an open-char, then parse the nested expressions.
+                if (supportsNesting && (input[current] == openChar))
+                {
+                    nestedCount++;
+                    try
+                    {
+                        // Check if we exceeded the number of nested calls.
+                        if (nestedCount > MaxNestedCount)
+                        {
+                            return HttpParseResult.InvalidFormat;
+                        }
+
+                        var nestedLength = 0;
+                        HttpParseResult nestedResult = GetExpressionLength(input, current, openChar, closeChar,
+                            supportsNesting, ref nestedCount, out nestedLength);
+
+                        switch (nestedResult)
+                        {
+                            case HttpParseResult.Parsed:
+                                current += nestedLength; // add the length of the nested expression and continue.
+                                break;
+
+                            case HttpParseResult.NotParsed:
+                                Contract.Assert(false, "'NotParsed' is unexpected: We started nested expression " +
+                                    "parsing, because we found the open-char. So either it's a valid nested " +
+                                    "expression or it has invalid format.");
+                                break;
+
+                            case HttpParseResult.InvalidFormat:
+                                // If the nested expression is invalid, we can't continue, so we fail with invalid format.
+                                return HttpParseResult.InvalidFormat;
+
+                            default:
+                                Contract.Assert(false, "Unknown enum result: " + nestedResult);
+                                break;
+                        }
+                    }
+                    finally
+                    {
+                        nestedCount--;
+                    }
+                }
+
+                if (input[current] == closeChar)
+                {
+                    length = current - startIndex + 1;
+                    return HttpParseResult.Parsed;
+                }
+                current++;
+            }
+
+            // We didn't see the final quote, therefore we have an invalid expression string.
+            return HttpParseResult.InvalidFormat;
+        }
+    }
+}
diff --git a/src/Http/Headers/src/MediaTypeHeaderValue.cs b/src/Http/Headers/src/MediaTypeHeaderValue.cs
new file mode 100644
index 0000000000000000000000000000000000000000..32074b44cc681b7aa7d67fefb6786e0647ed68a1
--- /dev/null
+++ b/src/Http/Headers/src/MediaTypeHeaderValue.cs
@@ -0,0 +1,721 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    /// <summary>
+    /// Representation of the media type header. See <see href="https://tools.ietf.org/html/rfc6838"/>.
+    /// </summary>
+    public class MediaTypeHeaderValue
+    {
+        private const string BoundaryString = "boundary";
+        private const string CharsetString = "charset";
+        private const string MatchesAllString = "*/*";
+        private const string QualityString = "q";
+        private const string WildcardString = "*";
+
+        private const char ForwardSlashCharacter = '/';
+        private const char PeriodCharacter = '.';
+        private const char PlusCharacter = '+';
+
+        private static readonly char[] PeriodCharacterArray = new char[] { PeriodCharacter };
+
+        private static readonly HttpHeaderParser<MediaTypeHeaderValue> SingleValueParser
+            = new GenericHeaderParser<MediaTypeHeaderValue>(false, GetMediaTypeLength);
+        private static readonly HttpHeaderParser<MediaTypeHeaderValue> MultipleValueParser
+            = new GenericHeaderParser<MediaTypeHeaderValue>(true, GetMediaTypeLength);
+
+        // Use a collection instead of a dictionary since we may have multiple parameters with the same name.
+        private ObjectCollection<NameValueHeaderValue> _parameters;
+        private StringSegment _mediaType;
+        private bool _isReadOnly;
+
+        private MediaTypeHeaderValue()
+        {
+            // Used by the parser to create a new instance of this type.
+        }
+
+        /// <summary>
+        /// Initializes a <see cref="MediaTypeHeaderValue"/> instance.
+        /// </summary>
+        /// <param name="mediaType">A <see cref="StringSegment"/> representation of a media type.
+        /// The text provided must be a single media type without parameters. </param>
+        public MediaTypeHeaderValue(StringSegment mediaType)
+        {
+            CheckMediaTypeFormat(mediaType, nameof(mediaType));
+            _mediaType = mediaType;
+        }
+
+        /// <summary>
+        /// Initializes a <see cref="MediaTypeHeaderValue"/> instance.
+        /// </summary>
+        /// <param name="mediaType">A <see cref="StringSegment"/> representation of a media type.
+        /// The text provided must be a single media type without parameters. </param>
+        /// <param name="quality">The <see cref="double"/> with the quality of the media type.</param>
+        public MediaTypeHeaderValue(StringSegment mediaType, double quality)
+            : this(mediaType)
+        {
+            Quality = quality;
+        }
+
+        /// <summary>
+        /// Gets or sets the value of the charset parameter. Returns <see cref="StringSegment.Empty"/>
+        /// if there is no charset.
+        /// </summary>
+        public StringSegment Charset
+        {
+            get
+            {
+                return NameValueHeaderValue.Find(_parameters, CharsetString)?.Value.Value;
+            }
+            set
+            {
+                HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
+                // We don't prevent a user from setting whitespace-only charsets. Like we can't prevent a user from
+                // setting a non-existing charset.
+                var charsetParameter = NameValueHeaderValue.Find(_parameters, CharsetString);
+                if (StringSegment.IsNullOrEmpty(value))
+                {
+                    // Remove charset parameter
+                    if (charsetParameter != null)
+                    {
+                        Parameters.Remove(charsetParameter);
+                    }
+                }
+                else
+                {
+                    if (charsetParameter != null)
+                    {
+                        charsetParameter.Value = value;
+                    }
+                    else
+                    {
+                        Parameters.Add(new NameValueHeaderValue(CharsetString, value));
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the value of the Encoding parameter. Setting the Encoding will set
+        /// the <see cref="Charset"/> to <see cref="Encoding.WebName"/>.
+        /// </summary>
+        public Encoding Encoding
+        {
+            get
+            {
+                var charset = Charset;
+                if (!StringSegment.IsNullOrEmpty(charset))
+                {
+                    try
+                    {
+                        return Encoding.GetEncoding(charset.Value);
+                    }
+                    catch (ArgumentException)
+                    {
+                        // Invalid or not supported
+                    }
+                }
+                return null;
+            }
+            set
+            {
+                HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
+                if (value == null)
+                {
+                    Charset = null;
+                }
+                else
+                {
+                    Charset = value.WebName;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the value of the boundary parameter. Returns <see cref="StringSegment.Empty"/>
+        /// if there is no boundary.
+        /// </summary>
+        public StringSegment Boundary
+        {
+            get
+            {
+                return NameValueHeaderValue.Find(_parameters, BoundaryString)?.Value ?? default(StringSegment);
+            }
+            set
+            {
+                HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
+                var boundaryParameter = NameValueHeaderValue.Find(_parameters, BoundaryString);
+                if (StringSegment.IsNullOrEmpty(value))
+                {
+                    // Remove charset parameter
+                    if (boundaryParameter != null)
+                    {
+                        Parameters.Remove(boundaryParameter);
+                    }
+                }
+                else
+                {
+                    if (boundaryParameter != null)
+                    {
+                        boundaryParameter.Value = value;
+                    }
+                    else
+                    {
+                        Parameters.Add(new NameValueHeaderValue(BoundaryString, value));
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the media type's parameters. Returns an empty <see cref="IList{T}"/>
+        /// if there are no parameters.
+        /// </summary>
+        public IList<NameValueHeaderValue> Parameters
+        {
+            get
+            {
+                if (_parameters == null)
+                {
+                    if (IsReadOnly)
+                    {
+                        _parameters = ObjectCollection<NameValueHeaderValue>.EmptyReadOnlyCollection;
+                    }
+                    else
+                    {
+                        _parameters = new ObjectCollection<NameValueHeaderValue>();
+                    }
+                }
+                return _parameters;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the value of the quality parameter. Returns null
+        /// if there is no quality.
+        /// </summary>
+        public double? Quality
+        {
+            get { return HeaderUtilities.GetQuality(_parameters); }
+            set
+            {
+                HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
+                HeaderUtilities.SetQuality(Parameters, value);
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the value of the media type. Returns <see cref="StringSegment.Empty"/>
+        /// if there is no media type.
+        /// </summary>
+        /// <example>
+        /// For the media type <c>"application/json"</c>, the property gives the value
+        /// <c>"application/json"</c>.
+        /// </example>
+        public StringSegment MediaType
+        {
+            get { return _mediaType; }
+            set
+            {
+                HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
+                CheckMediaTypeFormat(value, nameof(value));
+                _mediaType = value;
+            }
+        }
+
+        /// <summary>
+        /// Gets the type of the <see cref="MediaTypeHeaderValue"/>.
+        /// </summary>
+        /// <example>
+        /// For the media type <c>"application/json"</c>, the property gives the value <c>"application"</c>.
+        /// </example>
+        /// <remarks>See <see href="https://tools.ietf.org/html/rfc6838#section-4.2"/> for more details on the type.</remarks>
+        public StringSegment Type
+        {
+            get
+            {
+                return _mediaType.Subsegment(0, _mediaType.IndexOf(ForwardSlashCharacter));
+            }
+        }
+
+        /// <summary>
+        /// Gets the subtype of the <see cref="MediaTypeHeaderValue"/>.
+        /// </summary>
+        /// <example>
+        /// For the media type <c>"application/vnd.example+json"</c>, the property gives the value
+        /// <c>"vnd.example+json"</c>.
+        /// </example>
+        /// <remarks>See <see href="https://tools.ietf.org/html/rfc6838#section-4.2"/> for more details on the subtype.</remarks>
+        public StringSegment SubType
+        {
+            get
+            {
+                return _mediaType.Subsegment(_mediaType.IndexOf(ForwardSlashCharacter) + 1);
+            }
+        }
+
+        /// <summary>
+        /// Gets subtype of the <see cref="MediaTypeHeaderValue"/>, excluding any structured syntax suffix. Returns <see cref="StringSegment.Empty"/>
+        /// if there is no subtype without suffix.
+        /// </summary>
+        /// <example>
+        /// For the media type <c>"application/vnd.example+json"</c>, the property gives the value
+        /// <c>"vnd.example"</c>.
+        /// </example>
+        public StringSegment SubTypeWithoutSuffix
+        {
+            get
+            {
+                var subType = SubType;
+                var startOfSuffix = subType.LastIndexOf(PlusCharacter);
+                if (startOfSuffix == -1)
+                {
+                    return subType;
+                }
+                else
+                {
+                    return subType.Subsegment(0, startOfSuffix);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets the structured syntax suffix of the <see cref="MediaTypeHeaderValue"/> if it has one.
+        /// See <see href="https://tools.ietf.org/html/rfc6838#section-4.8">The RFC documentation on structured syntaxes.</see>
+        /// </summary>
+        /// <example>
+        /// For the media type <c>"application/vnd.example+json"</c>, the property gives the value
+        /// <c>"json"</c>.
+        /// </example>
+        public StringSegment Suffix
+        {
+            get
+            {
+                var subType = SubType;
+                var startOfSuffix = subType.LastIndexOf(PlusCharacter);
+                if (startOfSuffix == -1)
+                {
+                    return default(StringSegment);
+                }
+                else
+                {
+                    return subType.Subsegment(startOfSuffix + 1);
+                }
+            }
+        }
+
+
+        /// <summary>
+        /// Get a <see cref="IList{T}"/> of facets of the <see cref="MediaTypeHeaderValue"/>. Facets are a
+        /// period separated list of StringSegments in the <see cref="SubTypeWithoutSuffix"/>.
+        /// See <see href="https://tools.ietf.org/html/rfc6838#section-3">The RFC documentation on facets.</see>
+        /// </summary>
+        /// <example>
+        /// For the media type <c>"application/vnd.example+json"</c>, the property gives the value:
+        /// <c>{"vnd", "example"}</c>
+        /// </example>
+        public IEnumerable<StringSegment> Facets
+        {
+            get
+            {
+                return SubTypeWithoutSuffix.Split(PeriodCharacterArray);
+            }
+        }
+
+        /// <summary>
+        /// Gets whether this <see cref="MediaTypeHeaderValue"/> matches all types.
+        /// </summary>
+        public bool MatchesAllTypes => MediaType.Equals(MatchesAllString, StringComparison.Ordinal);
+
+        /// <summary>
+        /// Gets whether this <see cref="MediaTypeHeaderValue"/> matches all subtypes.
+        /// </summary>
+        /// <example>
+        /// For the media type <c>"application/*"</c>, this property is <c>true</c>.
+        /// </example>
+        /// <example>
+        /// For the media type <c>"application/json"</c>, this property is <c>false</c>.
+        /// </example>
+        public bool MatchesAllSubTypes => SubType.Equals(WildcardString, StringComparison.Ordinal);
+
+        /// <summary>
+        /// Gets whether this <see cref="MediaTypeHeaderValue"/> matches all subtypes, ignoring any structured syntax suffix.
+        /// </summary>
+        /// <example>
+        /// For the media type <c>"application/*+json"</c>, this property is <c>true</c>.
+        /// </example>
+        /// <example>
+        /// For the media type <c>"application/vnd.example+json"</c>, this property is <c>false</c>.
+        /// </example>
+        public bool MatchesAllSubTypesWithoutSuffix =>
+            SubTypeWithoutSuffix.Equals(WildcardString, StringComparison.OrdinalIgnoreCase);
+
+        /// <summary>
+        /// Gets whether the <see cref="MediaTypeHeaderValue"/> is readonly.
+        /// </summary>
+        public bool IsReadOnly
+        {
+            get { return _isReadOnly; }
+        }
+
+        /// <summary>
+        /// Gets a value indicating whether this <see cref="MediaTypeHeaderValue"/> is a subset of
+        /// <paramref name="otherMediaType"/>. A "subset" is defined as the same or a more specific media type
+        /// according to the precedence described in https://www.ietf.org/rfc/rfc2068.txt section 14.1, Accept.
+        /// </summary>
+        /// <param name="otherMediaType">The <see cref="MediaTypeHeaderValue"/> to compare.</param>
+        /// <returns>
+        /// A value indicating whether this <see cref="MediaTypeHeaderValue"/> is a subset of
+        /// <paramref name="otherMediaType"/>.
+        /// </returns>
+        /// <remarks>
+        /// For example "multipart/mixed; boundary=1234" is a subset of "multipart/mixed; boundary=1234",
+        /// "multipart/mixed", "multipart/*", and "*/*" but not "multipart/mixed; boundary=2345" or
+        /// "multipart/message; boundary=1234".
+        /// </remarks>
+        public bool IsSubsetOf(MediaTypeHeaderValue otherMediaType)
+        {
+            if (otherMediaType == null)
+            {
+                return false;
+            }
+
+            // "text/plain" is a subset of "text/plain", "text/*" and "*/*". "*/*" is a subset only of "*/*".
+            return MatchesType(otherMediaType) &&
+                MatchesSubtype(otherMediaType) &&
+                MatchesParameters(otherMediaType);
+        }
+
+        /// <summary>
+        /// Performs a deep copy of this object and all of it's NameValueHeaderValue sub components,
+        /// while avoiding the cost of re-validating the components.
+        /// </summary>
+        /// <returns>A deep copy.</returns>
+        public MediaTypeHeaderValue Copy()
+        {
+            var other = new MediaTypeHeaderValue();
+            other._mediaType = _mediaType;
+
+            if (_parameters != null)
+            {
+                other._parameters = new ObjectCollection<NameValueHeaderValue>(
+                    _parameters.Select(item => item.Copy()));
+            }
+            return other;
+        }
+
+        /// <summary>
+        /// Performs a deep copy of this object and all of it's NameValueHeaderValue sub components,
+        /// while avoiding the cost of re-validating the components. This copy is read-only.
+        /// </summary>
+        /// <returns>A deep, read-only, copy.</returns>
+        public MediaTypeHeaderValue CopyAsReadOnly()
+        {
+            if (IsReadOnly)
+            {
+                return this;
+            }
+
+            var other = new MediaTypeHeaderValue();
+            other._mediaType = _mediaType;
+            if (_parameters != null)
+            {
+                other._parameters = new ObjectCollection<NameValueHeaderValue>(
+                    _parameters.Select(item => item.CopyAsReadOnly()), isReadOnly: true);
+            }
+            other._isReadOnly = true;
+            return other;
+        }
+
+        public override string ToString()
+        {
+            var builder = new StringBuilder();
+            builder.Append(_mediaType);
+            NameValueHeaderValue.ToString(_parameters, separator: ';', leadingSeparator: true, destination: builder);
+            return builder.ToString();
+        }
+
+        public override bool Equals(object obj)
+        {
+            var other = obj as MediaTypeHeaderValue;
+
+            if (other == null)
+            {
+                return false;
+            }
+
+            return _mediaType.Equals(other._mediaType, StringComparison.OrdinalIgnoreCase) &&
+                HeaderUtilities.AreEqualCollections(_parameters, other._parameters);
+        }
+
+        public override int GetHashCode()
+        {
+            // The media-type string is case-insensitive.
+            return StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_mediaType) ^ NameValueHeaderValue.GetHashCode(_parameters);
+        }
+
+        /// <summary>
+        /// Takes a media type and parses it into the <see cref="MediaTypeHeaderValue" /> and its associated parameters.
+        /// </summary>
+        /// <param name="input">The <see cref="StringSegment"/> with the media type.</param>
+        /// <returns>The parsed <see cref="MediaTypeHeaderValue"/>.</returns>
+        public static MediaTypeHeaderValue Parse(StringSegment input)
+        {
+            var index = 0;
+            return SingleValueParser.ParseValue(input, ref index);
+        }
+
+        /// <summary>
+        /// Takes a media type, which can include parameters, and parses it into the <see cref="MediaTypeHeaderValue" /> and its associated parameters.
+        /// </summary>
+        /// <param name="input">The <see cref="StringSegment"/> with the media type. The media type constructed here must not have an y</param>
+        /// <param name="parsedValue">The parsed <see cref="MediaTypeHeaderValue"/></param>
+        /// <returns>True if the value was successfully parsed.</returns>
+        public static bool TryParse(StringSegment input, out MediaTypeHeaderValue parsedValue)
+        {
+            var index = 0;
+            return SingleValueParser.TryParseValue(input, ref index, out parsedValue);
+        }
+
+        /// <summary>
+        /// Takes an <see cref="IList{T}"/> of <see cref="string"/> and parses it into the <see cref="MediaTypeHeaderValue"></see> and its associated parameters.
+        /// </summary>
+        /// <param name="inputs">A list of media types</param>
+        /// <returns>The parsed <see cref="MediaTypeHeaderValue"/>.</returns>
+        public static IList<MediaTypeHeaderValue> ParseList(IList<string> inputs)
+        {
+            return MultipleValueParser.ParseValues(inputs);
+        }
+
+        /// <summary>
+        /// Takes an <see cref="IList{T}"/> of <see cref="string"/> and parses it into the <see cref="MediaTypeHeaderValue"></see> and its associated parameters.
+        /// Throws if there is invalid data in a string.
+        /// </summary>
+        /// <param name="inputs">A list of media types</param>
+        /// <returns>The parsed <see cref="MediaTypeHeaderValue"/>.</returns>
+        public static IList<MediaTypeHeaderValue> ParseStrictList(IList<string> inputs)
+        {
+            return MultipleValueParser.ParseStrictValues(inputs);
+        }
+
+        /// <summary>
+        /// Takes an <see cref="IList{T}"/> of <see cref="string"/> and parses it into the <see cref="MediaTypeHeaderValue"></see> and its associated parameters.
+        /// </summary>
+        /// <param name="inputs">A list of media types</param>
+        /// <param name="parsedValues">The parsed <see cref="MediaTypeHeaderValue"/>.</param>
+        /// <returns>True if the value was successfully parsed.</returns>
+        public static bool TryParseList(IList<string> inputs, out IList<MediaTypeHeaderValue> parsedValues)
+        {
+            return MultipleValueParser.TryParseValues(inputs, out parsedValues);
+        }
+
+        /// <summary>
+        /// Takes an <see cref="IList{T}"/> of <see cref="string"/> and parses it into the <see cref="MediaTypeHeaderValue"></see> and its associated parameters.
+        /// </summary>
+        /// <param name="inputs">A list of media types</param>
+        /// <param name="parsedValues">The parsed <see cref="MediaTypeHeaderValue"/>.</param>
+        /// <returns>True if the value was successfully parsed.</returns>
+        public static bool TryParseStrictList(IList<string> inputs, out IList<MediaTypeHeaderValue> parsedValues)
+        {
+            return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
+        }
+
+        private static int GetMediaTypeLength(StringSegment input, int startIndex, out MediaTypeHeaderValue parsedValue)
+        {
+            Contract.Requires(startIndex >= 0);
+
+            parsedValue = null;
+
+            if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+            {
+                return 0;
+            }
+
+            // Caller must remove leading whitespace. If not, we'll return 0.
+            var mediaTypeLength = MediaTypeHeaderValue.GetMediaTypeExpressionLength(input, startIndex, out var mediaType);
+
+            if (mediaTypeLength == 0)
+            {
+                return 0;
+            }
+
+            var current = startIndex + mediaTypeLength;
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+            MediaTypeHeaderValue mediaTypeHeader = null;
+
+            // If we're not done and we have a parameter delimiter, then we have a list of parameters.
+            if ((current < input.Length) && (input[current] == ';'))
+            {
+                mediaTypeHeader = new MediaTypeHeaderValue();
+                mediaTypeHeader._mediaType = mediaType;
+
+                current++; // skip delimiter.
+                var parameterLength = NameValueHeaderValue.GetNameValueListLength(input, current, ';',
+                    mediaTypeHeader.Parameters);
+
+                parsedValue = mediaTypeHeader;
+                return current + parameterLength - startIndex;
+            }
+
+            // We have a media type without parameters.
+            mediaTypeHeader = new MediaTypeHeaderValue();
+            mediaTypeHeader._mediaType = mediaType;
+            parsedValue = mediaTypeHeader;
+            return current - startIndex;
+        }
+
+        private static int GetMediaTypeExpressionLength(StringSegment input, int startIndex, out StringSegment mediaType)
+        {
+            Contract.Requires((input != null) && (input.Length > 0) && (startIndex < input.Length));
+
+            // This method just parses the "type/subtype" string, it does not parse parameters.
+            mediaType = null;
+
+            // Parse the type, i.e. <type> in media type string "<type>/<subtype>; param1=value1; param2=value2"
+            var typeLength = HttpRuleParser.GetTokenLength(input, startIndex);
+
+            if (typeLength == 0)
+            {
+                return 0;
+            }
+
+            var current = startIndex + typeLength;
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+            // Parse the separator between type and subtype
+            if ((current >= input.Length) || (input[current] != '/'))
+            {
+                return 0;
+            }
+            current++; // skip delimiter.
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+            // Parse the subtype, i.e. <subtype> in media type string "<type>/<subtype>; param1=value1; param2=value2"
+            var subtypeLength = HttpRuleParser.GetTokenLength(input, current);
+
+            if (subtypeLength == 0)
+            {
+                return 0;
+            }
+
+            // If there is no whitespace between <type> and <subtype> in <type>/<subtype> get the media type using
+            // one Substring call. Otherwise get substrings for <type> and <subtype> and combine them.
+            var mediaTypeLength = current + subtypeLength - startIndex;
+            if (typeLength + subtypeLength + 1 == mediaTypeLength)
+            {
+                mediaType = input.Subsegment(startIndex, mediaTypeLength);
+            }
+            else
+            {
+                mediaType = input.Substring(startIndex, typeLength) + ForwardSlashCharacter + input.Substring(current, subtypeLength);
+            }
+
+            return mediaTypeLength;
+        }
+
+        private static void CheckMediaTypeFormat(StringSegment mediaType, string parameterName)
+        {
+            if (StringSegment.IsNullOrEmpty(mediaType))
+            {
+                throw new ArgumentException("An empty string is not allowed.", parameterName);
+            }
+
+            // When adding values using strongly typed objects, no leading/trailing LWS (whitespace) is allowed.
+            // Also no LWS between type and subtype is allowed.
+            var mediaTypeLength = GetMediaTypeExpressionLength(mediaType, 0, out var tempMediaType);
+            if ((mediaTypeLength == 0) || (tempMediaType.Length != mediaType.Length))
+            {
+                throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Invalid media type '{0}'.", mediaType));
+            }
+        }
+
+        private bool MatchesType(MediaTypeHeaderValue set)
+        {
+            return set.MatchesAllTypes ||
+                set.Type.Equals(Type, StringComparison.OrdinalIgnoreCase);
+        }
+
+        private bool MatchesSubtype(MediaTypeHeaderValue set)
+        {
+            if (set.MatchesAllSubTypes)
+            {
+                return true;
+            }
+            if (set.Suffix.HasValue)
+            {
+                if (Suffix.HasValue)
+                {
+                    return MatchesSubtypeWithoutSuffix(set) && MatchesSubtypeSuffix(set);
+                }
+                else
+                {
+                    return false;
+                }
+            }
+            else
+            {
+                return set.SubType.Equals(SubType, StringComparison.OrdinalIgnoreCase);
+            }
+        }
+
+        private bool MatchesSubtypeWithoutSuffix(MediaTypeHeaderValue set)
+        {
+            return set.MatchesAllSubTypesWithoutSuffix ||
+                set.SubTypeWithoutSuffix.Equals(SubTypeWithoutSuffix, StringComparison.OrdinalIgnoreCase);
+        }
+
+        private bool MatchesParameters(MediaTypeHeaderValue set)
+        {
+            if (set._parameters != null && set._parameters.Count != 0)
+            {
+                // Make sure all parameters in the potential superset are included locally. Fine to have additional
+                // parameters locally; they make this one more specific.
+                foreach (var parameter in set._parameters)
+                {
+                    if (parameter.Name.Equals(WildcardString, StringComparison.OrdinalIgnoreCase))
+                    {
+                        // A parameter named "*" has no effect on media type matching, as it is only used as an indication
+                        // that the entire media type string should be treated as a wildcard.
+                        continue;
+                    }
+
+                    if (parameter.Name.Equals(QualityString, StringComparison.OrdinalIgnoreCase))
+                    {
+                        // "q" and later parameters are not involved in media type matching. Quoting the RFC: The first
+                        // "q" parameter (if any) separates the media-range parameter(s) from the accept-params.
+                        break;
+                    }
+
+                    var localParameter = NameValueHeaderValue.Find(_parameters, parameter.Name);
+                    if (localParameter == null)
+                    {
+                        // Not found.
+                        return false;
+                    }
+
+                    if (!StringSegment.Equals(parameter.Value, localParameter.Value, StringComparison.OrdinalIgnoreCase))
+                    {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+
+        private bool MatchesSubtypeSuffix(MediaTypeHeaderValue set)
+        {
+            // We don't have support for wildcards on suffixes alone (e.g., "application/entity+*")
+            // because there's no clear use case for it.
+            return set.Suffix.Equals(Suffix, StringComparison.OrdinalIgnoreCase);
+        }
+    }
+}
diff --git a/src/Http/Headers/src/MediaTypeHeaderValueComparer.cs b/src/Http/Headers/src/MediaTypeHeaderValueComparer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..cc34640988b50e9431b446e89073d02c359641fd
--- /dev/null
+++ b/src/Http/Headers/src/MediaTypeHeaderValueComparer.cs
@@ -0,0 +1,132 @@
+// 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;
+
+namespace Microsoft.Net.Http.Headers
+{
+    /// <summary>
+    /// Implementation of <see cref="IComparer{T}"/> that can compare accept media type header fields
+    /// based on their quality values (a.k.a q-values).
+    /// </summary>
+    public class MediaTypeHeaderValueComparer : IComparer<MediaTypeHeaderValue>
+    {
+        private static readonly MediaTypeHeaderValueComparer _mediaTypeComparer =
+                                                                    new MediaTypeHeaderValueComparer();
+
+        private MediaTypeHeaderValueComparer()
+        {
+        }
+
+        public static MediaTypeHeaderValueComparer QualityComparer
+        {
+            get { return _mediaTypeComparer; }
+        }
+
+        /// <inheritdoc />
+        /// <remarks>
+        /// Performs comparisons based on the arguments' quality values
+        /// (aka their "q-value"). Values with identical q-values are considered equal (i.e. the result is 0)
+        /// with the exception that suffixed subtype wildcards are considered less than subtype wildcards, subtype wildcards
+        /// are considered less than specific media types and full wildcards are considered less than
+        /// subtype wildcards. This allows callers to sort a sequence of <see cref="MediaTypeHeaderValue"/> following
+        /// their q-values in the order of specific media types, subtype wildcards, and last any full wildcards.
+        /// </remarks>
+        /// <example>
+        /// If we had a list of media types (comma separated): { text/*;q=0.8, text/*+json;q=0.8, */*;q=1, */*;q=0.8, text/plain;q=0.8 }
+        /// Sorting them using Compare would return: { */*;q=0.8, text/*;q=0.8, text/*+json;q=0.8, text/plain;q=0.8, */*;q=1 }
+        /// </example>
+        public int Compare(MediaTypeHeaderValue mediaType1, MediaTypeHeaderValue mediaType2)
+        {
+            if (object.ReferenceEquals(mediaType1, mediaType2))
+            {
+                return 0;
+            }
+
+            var returnValue = CompareBasedOnQualityFactor(mediaType1, mediaType2);
+
+            if (returnValue == 0)
+            {
+                if (!mediaType1.Type.Equals(mediaType2.Type, StringComparison.OrdinalIgnoreCase))
+                {
+                    if (mediaType1.MatchesAllTypes)
+                    {
+                        return -1;
+                    }
+                    else if (mediaType2.MatchesAllTypes)
+                    {
+                        return 1;
+                    }
+                    else if (mediaType1.MatchesAllSubTypes && !mediaType2.MatchesAllSubTypes)
+                    {
+                        return -1;
+                    }
+                    else if (!mediaType1.MatchesAllSubTypes && mediaType2.MatchesAllSubTypes)
+                    {
+                        return 1;
+                    }
+                    else if (mediaType1.MatchesAllSubTypesWithoutSuffix && !mediaType2.MatchesAllSubTypesWithoutSuffix)
+                    {
+                        return -1;
+                    }
+                    else if (!mediaType1.MatchesAllSubTypesWithoutSuffix && mediaType2.MatchesAllSubTypesWithoutSuffix)
+                    {
+                        return 1;
+                    }
+                }
+                else if (!mediaType1.SubType.Equals(mediaType2.SubType, StringComparison.OrdinalIgnoreCase))
+                {
+                    if (mediaType1.MatchesAllSubTypes)
+                    {
+                        return -1;
+                    }
+                    else if (mediaType2.MatchesAllSubTypes)
+                    {
+                        return 1;
+                    }
+                    else if (mediaType1.MatchesAllSubTypesWithoutSuffix && !mediaType2.MatchesAllSubTypesWithoutSuffix)
+                    {
+                        return -1;
+                    }
+                    else if (!mediaType1.MatchesAllSubTypesWithoutSuffix && mediaType2.MatchesAllSubTypesWithoutSuffix)
+                    {
+                        return 1;
+                    }
+                }
+                else if (!mediaType1.Suffix.Equals(mediaType2.Suffix, StringComparison.OrdinalIgnoreCase))
+                {
+                    if (mediaType1.MatchesAllSubTypesWithoutSuffix)
+                    {
+                        return -1;
+                    }
+                    else if (mediaType2.MatchesAllSubTypesWithoutSuffix)
+                    {
+                        return 1;
+                    }
+                }
+            }
+
+            return returnValue;
+        }
+
+        private static int CompareBasedOnQualityFactor(
+            MediaTypeHeaderValue mediaType1,
+            MediaTypeHeaderValue mediaType2)
+        {
+            var mediaType1Quality = mediaType1.Quality ?? HeaderQuality.Match;
+            var mediaType2Quality = mediaType2.Quality ?? HeaderQuality.Match;
+            var qualityDifference = mediaType1Quality - mediaType2Quality;
+            if (qualityDifference < 0)
+            {
+                return -1;
+            }
+            else if (qualityDifference > 0)
+            {
+                return 1;
+            }
+
+            return 0;
+        }
+    }
+}
diff --git a/src/Http/Headers/src/Microsoft.Net.Http.Headers.csproj b/src/Http/Headers/src/Microsoft.Net.Http.Headers.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..80b0f49989e9207f0e14d795667cb08bcd72cd63
--- /dev/null
+++ b/src/Http/Headers/src/Microsoft.Net.Http.Headers.csproj
@@ -0,0 +1,17 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>HTTP header parser implementations.</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <NoWarn>$(NoWarn);CS1591</NoWarn>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>http</PackageTags>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.Extensions.Primitives" />
+    <Reference Include="System.Buffers" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/Headers/src/NameValueHeaderValue.cs b/src/Http/Headers/src/NameValueHeaderValue.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ba197e986c3ad1a3a73f0f6661970115f2254282
--- /dev/null
+++ b/src/Http/Headers/src/NameValueHeaderValue.cs
@@ -0,0 +1,425 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Globalization;
+using System.Text;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    // According to the RFC, in places where a "parameter" is required, the value is mandatory
+    // (e.g. Media-Type, Accept). However, we don't introduce a dedicated type for it. So NameValueHeaderValue supports
+    // name-only values in addition to name/value pairs.
+    public class NameValueHeaderValue
+    {
+        private static readonly HttpHeaderParser<NameValueHeaderValue> SingleValueParser
+            = new GenericHeaderParser<NameValueHeaderValue>(false, GetNameValueLength);
+        internal static readonly HttpHeaderParser<NameValueHeaderValue> MultipleValueParser
+            = new GenericHeaderParser<NameValueHeaderValue>(true, GetNameValueLength);
+
+        private StringSegment _name;
+        private StringSegment _value;
+        private bool _isReadOnly;
+
+        private NameValueHeaderValue()
+        {
+            // Used by the parser to create a new instance of this type.
+        }
+
+        public NameValueHeaderValue(StringSegment name)
+            : this(name, null)
+        {
+        }
+
+        public NameValueHeaderValue(StringSegment name, StringSegment value)
+        {
+            CheckNameValueFormat(name, value);
+
+            _name = name;
+            _value = value;
+        }
+
+        public StringSegment Name
+        {
+            get { return _name; }
+        }
+
+        public StringSegment Value
+        {
+            get { return _value; }
+            set
+            {
+                HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
+                CheckValueFormat(value);
+                _value = value;
+            }
+        }
+
+        public bool IsReadOnly { get { return _isReadOnly; } }
+
+        /// <summary>
+        /// Provides a copy of this object without the cost of re-validating the values.
+        /// </summary>
+        /// <returns>A copy.</returns>
+        public NameValueHeaderValue Copy()
+        {
+            return new NameValueHeaderValue()
+            {
+                _name = _name,
+                _value = _value
+            };
+        }
+
+        public NameValueHeaderValue CopyAsReadOnly()
+        {
+            if (IsReadOnly)
+            {
+                return this;
+            }
+
+            return new NameValueHeaderValue()
+            {
+                _name = _name,
+                _value = _value,
+                _isReadOnly = true
+            };
+        }
+
+        public override int GetHashCode()
+        {
+            Contract.Assert(_name != null);
+
+            var nameHashCode = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_name);
+
+            if (!StringSegment.IsNullOrEmpty(_value))
+            {
+                // If we have a quoted-string, then just use the hash code. If we have a token, convert to lowercase
+                // and retrieve the hash code.
+                if (_value[0] == '"')
+                {
+                    return nameHashCode ^ _value.GetHashCode();
+                }
+
+                return nameHashCode ^ StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_value);
+            }
+
+            return nameHashCode;
+        }
+
+        public override bool Equals(object obj)
+        {
+            var other = obj as NameValueHeaderValue;
+
+            if (other == null)
+            {
+                return false;
+            }
+
+            if (!_name.Equals(other._name, StringComparison.OrdinalIgnoreCase))
+            {
+                return false;
+            }
+
+            // RFC2616: 14.20: unquoted tokens should use case-INsensitive comparison; quoted-strings should use
+            // case-sensitive comparison. The RFC doesn't mention how to compare quoted-strings outside the "Expect"
+            // header. We treat all quoted-strings the same: case-sensitive comparison.
+
+            if (StringSegment.IsNullOrEmpty(_value))
+            {
+                return StringSegment.IsNullOrEmpty(other._value);
+            }
+
+            if (_value[0] == '"')
+            {
+                // We have a quoted string, so we need to do case-sensitive comparison.
+                return (_value.Equals(other._value, StringComparison.Ordinal));
+            }
+            else
+            {
+                return (_value.Equals(other._value, StringComparison.OrdinalIgnoreCase));
+            }
+        }
+
+        public StringSegment GetUnescapedValue()
+        {
+            if (!HeaderUtilities.IsQuoted(_value))
+            {
+                return _value;
+            }
+            return HeaderUtilities.UnescapeAsQuotedString(_value);
+        }
+
+        public void SetAndEscapeValue(StringSegment value)
+        {
+            HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
+            if (StringSegment.IsNullOrEmpty(value) || (GetValueLength(value, 0) == value.Length))
+            {
+                _value = value;
+            }
+            else
+            {
+                Value = HeaderUtilities.EscapeAsQuotedString(value);
+            }
+        }
+
+        public static NameValueHeaderValue Parse(StringSegment input)
+        {
+            var index = 0;
+            return SingleValueParser.ParseValue(input, ref index);
+        }
+
+        public static bool TryParse(StringSegment input, out NameValueHeaderValue parsedValue)
+        {
+            var index = 0;
+            return SingleValueParser.TryParseValue(input, ref index, out parsedValue);
+        }
+
+        public static IList<NameValueHeaderValue> ParseList(IList<string> input)
+        {
+            return MultipleValueParser.ParseValues(input);
+        }
+
+        public static IList<NameValueHeaderValue> ParseStrictList(IList<string> input)
+        {
+            return MultipleValueParser.ParseStrictValues(input);
+        }
+
+        public static bool TryParseList(IList<string> input, out IList<NameValueHeaderValue> parsedValues)
+        {
+            return MultipleValueParser.TryParseValues(input, out parsedValues);
+        }
+
+        public static bool TryParseStrictList(IList<string> input, out IList<NameValueHeaderValue> parsedValues)
+        {
+            return MultipleValueParser.TryParseStrictValues(input, out parsedValues);
+        }
+
+        public override string ToString()
+        {
+            if (!StringSegment.IsNullOrEmpty(_value))
+            {
+                return _name + "=" + _value;
+            }
+            return _name.ToString();
+        }
+
+        internal static void ToString(
+            IList<NameValueHeaderValue> values,
+            char separator,
+            bool leadingSeparator,
+            StringBuilder destination)
+        {
+            Contract.Assert(destination != null);
+
+            if ((values == null) || (values.Count == 0))
+            {
+                return;
+            }
+
+            for (var i = 0; i < values.Count; i++)
+            {
+                if (leadingSeparator || (destination.Length > 0))
+                {
+                    destination.Append(separator);
+                    destination.Append(' ');
+                }
+                destination.Append(values[i].Name);
+                if (!StringSegment.IsNullOrEmpty(values[i].Value))
+                {
+                    destination.Append('=');
+                    destination.Append(values[i].Value);
+                }
+            }
+        }
+
+        internal static string ToString(IList<NameValueHeaderValue> values, char separator, bool leadingSeparator)
+        {
+            if ((values == null) || (values.Count == 0))
+            {
+                return null;
+            }
+
+            var sb = new StringBuilder();
+
+            ToString(values, separator, leadingSeparator, sb);
+
+            return sb.ToString();
+        }
+
+        internal static int GetHashCode(IList<NameValueHeaderValue> values)
+        {
+            if ((values == null) || (values.Count == 0))
+            {
+                return 0;
+            }
+
+            var result = 0;
+            for (var i = 0; i < values.Count; i++)
+            {
+                result = result ^ values[i].GetHashCode();
+            }
+            return result;
+        }
+
+        private static int GetNameValueLength(StringSegment input, int startIndex, out NameValueHeaderValue parsedValue)
+        {
+            Contract.Requires(input != null);
+            Contract.Requires(startIndex >= 0);
+
+            parsedValue = null;
+
+            if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+            {
+                return 0;
+            }
+
+            // Parse the name, i.e. <name> in name/value string "<name>=<value>". Caller must remove
+            // leading whitespaces.
+            var nameLength = HttpRuleParser.GetTokenLength(input, startIndex);
+
+            if (nameLength == 0)
+            {
+                return 0;
+            }
+
+            var name = input.Subsegment(startIndex, nameLength);
+            var current = startIndex + nameLength;
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+            // Parse the separator between name and value
+            if ((current == input.Length) || (input[current] != '='))
+            {
+                // We only have a name and that's OK. Return.
+                parsedValue = new NameValueHeaderValue();
+                parsedValue._name = name;
+                current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces
+                return current - startIndex;
+            }
+
+            current++; // skip delimiter.
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+            // Parse the value, i.e. <value> in name/value string "<name>=<value>"
+            int valueLength = GetValueLength(input, current);
+
+            // Value after the '=' may be empty
+            // Use parameterless ctor to avoid double-parsing of name and value, i.e. skip public ctor validation.
+            parsedValue = new NameValueHeaderValue();
+            parsedValue._name = name;
+            parsedValue._value = input.Subsegment(current, valueLength);
+            current = current + valueLength;
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current); // skip whitespaces
+            return current - startIndex;
+        }
+
+        // Returns the length of a name/value list, separated by 'delimiter'. E.g. "a=b, c=d, e=f" adds 3
+        // name/value pairs to 'nameValueCollection' if 'delimiter' equals ','.
+        internal static int GetNameValueListLength(
+            StringSegment input,
+            int startIndex,
+            char delimiter,
+            IList<NameValueHeaderValue> nameValueCollection)
+        {
+            Contract.Requires(nameValueCollection != null);
+            Contract.Requires(startIndex >= 0);
+
+            if ((StringSegment.IsNullOrEmpty(input)) || (startIndex >= input.Length))
+            {
+                return 0;
+            }
+
+            var current = startIndex + HttpRuleParser.GetWhitespaceLength(input, startIndex);
+            while (true)
+            {
+                NameValueHeaderValue parameter = null;
+                var nameValueLength = GetNameValueLength(input, current, out parameter);
+
+                if (nameValueLength == 0)
+                {
+                    // There may be a trailing ';'
+                    return current - startIndex;
+                }
+
+                nameValueCollection.Add(parameter);
+                current = current + nameValueLength;
+                current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+                if ((current == input.Length) || (input[current] != delimiter))
+                {
+                    // We're done and we have at least one valid name/value pair.
+                    return current - startIndex;
+                }
+
+                // input[current] is 'delimiter'. Skip the delimiter and whitespaces and try to parse again.
+                current++; // skip delimiter.
+                current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+            }
+        }
+
+        public static NameValueHeaderValue Find(IList<NameValueHeaderValue> values, StringSegment name)
+        {
+            Contract.Requires((name != null) && (name.Length > 0));
+
+            if ((values == null) || (values.Count == 0))
+            {
+                return null;
+            }
+
+            for (var i = 0; i < values.Count; i++)
+            {
+                var value = values[i];
+                if (value.Name.Equals(name, StringComparison.OrdinalIgnoreCase))
+                {
+                    return value;
+                }
+            }
+            return null;
+        }
+
+        internal static int GetValueLength(StringSegment input, int startIndex)
+        {
+            Contract.Requires(input != null);
+
+            if (startIndex >= input.Length)
+            {
+                return 0;
+            }
+
+            var valueLength = HttpRuleParser.GetTokenLength(input, startIndex);
+
+            if (valueLength == 0)
+            {
+                // A value can either be a token or a quoted string. Check if it is a quoted string.
+                if (HttpRuleParser.GetQuotedStringLength(input, startIndex, out valueLength) != HttpParseResult.Parsed)
+                {
+                    // We have an invalid value. Reset the name and return.
+                    return 0;
+                }
+            }
+            return valueLength;
+        }
+
+        private static void CheckNameValueFormat(StringSegment name, StringSegment value)
+        {
+            HeaderUtilities.CheckValidToken(name, nameof(name));
+            CheckValueFormat(value);
+        }
+
+        private static void CheckValueFormat(StringSegment value)
+        {
+            // Either value is null/empty or a valid token/quoted string
+            if (!(StringSegment.IsNullOrEmpty(value) || (GetValueLength(value, 0) == value.Length)))
+            {
+                throw new FormatException(string.Format(CultureInfo.InvariantCulture, "The header value is invalid: '{0}'", value));
+            }
+        }
+
+        private static NameValueHeaderValue CreateNameValue()
+        {
+            return new NameValueHeaderValue();
+        }
+    }
+}
diff --git a/src/Http/Headers/src/ObjectCollection.cs b/src/Http/Headers/src/ObjectCollection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..db5f876b53d3a7cd1fa04eae04865dfa3049abdb
--- /dev/null
+++ b/src/Http/Headers/src/ObjectCollection.cs
@@ -0,0 +1,91 @@
+// 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.Collections.ObjectModel;
+
+namespace Microsoft.Net.Http.Headers
+{
+    // List<T> allows 'null' values to be added. This is not what we want so we use a custom Collection<T> derived
+    // type to throw if 'null' gets added. Collection<T> internally uses List<T> which comes at some cost. In addition
+    // Collection<T>.Add() calls List<T>.InsertItem() which is an O(n) operation (compared to O(1) for List<T>.Add()).
+    // This type is only used for very small collections (1-2 items) to keep the impact of using Collection<T> small.
+    internal class ObjectCollection<T> : Collection<T>
+    {
+        internal static readonly Action<T> DefaultValidator = CheckNotNull;
+        internal static readonly ObjectCollection<T> EmptyReadOnlyCollection
+            = new ObjectCollection<T>(DefaultValidator, isReadOnly: true);
+
+        private readonly Action<T> _validator;
+
+        // We need to create a 'read-only' inner list for Collection<T> to do the right
+        // thing.
+        private static IList<T> CreateInnerList(bool isReadOnly, IEnumerable <T> other = null)
+        {
+            var list = other == null ? new List<T>() : new List<T>(other);
+            if (isReadOnly)
+            {
+                return new ReadOnlyCollection<T>(list);
+            }
+            else
+            {
+                return list;
+            }
+        }
+
+        public ObjectCollection()
+            : this(DefaultValidator)
+        {
+        }
+
+        public ObjectCollection(Action<T> validator, bool isReadOnly = false)
+            : base(CreateInnerList(isReadOnly))
+        {
+            _validator = validator;
+        }
+
+        public ObjectCollection(IEnumerable<T> other, bool isReadOnly = false)
+            : base(CreateInnerList(isReadOnly, other))
+        {
+            _validator = DefaultValidator;
+            foreach (T item in Items)
+            {
+                _validator(item);
+            }
+        }
+
+        public bool IsReadOnly => ((ICollection<T>)this).IsReadOnly;
+
+        protected override void ClearItems()
+        {
+            base.ClearItems();
+        }
+
+        protected override void InsertItem(int index, T item)
+        {
+            _validator(item);
+            base.InsertItem(index, item);
+        }
+
+        protected override void RemoveItem(int index)
+        {
+            base.RemoveItem(index);
+        }
+
+        protected override void SetItem(int index, T item)
+        {
+            _validator(item);
+            base.SetItem(index, item);
+        }
+
+        private static void CheckNotNull(T item)
+        {
+            // null values cannot be added to the collection.
+            if (item == null)
+            {
+                throw new ArgumentNullException(nameof(item));
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Headers/src/Properties/AssemblyInfo.cs b/src/Http/Headers/src/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c876def487f0176ffb2ac6b81cad26ffb1b042b0
--- /dev/null
+++ b/src/Http/Headers/src/Properties/AssemblyInfo.cs
@@ -0,0 +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.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.Net.Http.Headers.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/Http/Headers/src/RangeConditionHeaderValue.cs b/src/Http/Headers/src/RangeConditionHeaderValue.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f1ebee276c587c94dd94830bd4fa9624fea7ea2e
--- /dev/null
+++ b/src/Http/Headers/src/RangeConditionHeaderValue.cs
@@ -0,0 +1,167 @@
+// 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.Contracts;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class RangeConditionHeaderValue
+    {
+        private static readonly HttpHeaderParser<RangeConditionHeaderValue> Parser
+            = new GenericHeaderParser<RangeConditionHeaderValue>(false, GetRangeConditionLength);
+
+        private DateTimeOffset? _lastModified;
+        private EntityTagHeaderValue _entityTag;
+
+        private RangeConditionHeaderValue()
+        {
+            // Used by the parser to create a new instance of this type.
+        }
+
+        public RangeConditionHeaderValue(DateTimeOffset lastModified)
+        {
+            _lastModified = lastModified;
+        }
+
+        public RangeConditionHeaderValue(EntityTagHeaderValue entityTag)
+        {
+            if (entityTag == null)
+            {
+                throw new ArgumentNullException(nameof(entityTag));
+            }
+
+            _entityTag = entityTag;
+        }
+
+        public RangeConditionHeaderValue(string entityTag)
+            : this(new EntityTagHeaderValue(entityTag))
+        {
+        }
+
+        public DateTimeOffset? LastModified
+        {
+            get { return _lastModified; }
+        }
+
+        public EntityTagHeaderValue EntityTag
+        {
+            get { return _entityTag; }
+        }
+
+        public override string ToString()
+        {
+            if (_entityTag == null)
+            {
+                return HeaderUtilities.FormatDate(_lastModified.Value);
+            }
+            return _entityTag.ToString();
+        }
+
+        public override bool Equals(object obj)
+        {
+            var other = obj as RangeConditionHeaderValue;
+
+            if (other == null)
+            {
+                return false;
+            }
+
+            if (_entityTag == null)
+            {
+                return (other._lastModified != null) && (_lastModified.Value == other._lastModified.Value);
+            }
+
+            return _entityTag.Equals(other._entityTag);
+        }
+
+        public override int GetHashCode()
+        {
+            if (_entityTag == null)
+            {
+                return _lastModified.Value.GetHashCode();
+            }
+
+            return _entityTag.GetHashCode();
+        }
+
+        public static RangeConditionHeaderValue Parse(StringSegment input)
+        {
+            var index = 0;
+            return Parser.ParseValue(input, ref index);
+        }
+
+        public static bool TryParse(StringSegment input, out RangeConditionHeaderValue parsedValue)
+        {
+            var index = 0;
+            return Parser.TryParseValue(input, ref index, out parsedValue);
+        }
+
+        private static int GetRangeConditionLength(StringSegment input, int startIndex, out RangeConditionHeaderValue parsedValue)
+        {
+            Contract.Requires(startIndex >= 0);
+
+            parsedValue = null;
+
+            // Make sure we have at least 2 characters
+            if (StringSegment.IsNullOrEmpty(input) || (startIndex + 1 >= input.Length))
+            {
+                return 0;
+            }
+
+            var current = startIndex;
+
+            // Caller must remove leading whitespaces.
+            DateTimeOffset date = DateTimeOffset.MinValue;
+            EntityTagHeaderValue entityTag = null;
+
+            // Entity tags are quoted strings optionally preceded by "W/". By looking at the first two character we
+            // can determine whether the string is en entity tag or a date.
+            var firstChar = input[current];
+            var secondChar = input[current + 1];
+
+            if ((firstChar == '\"') || (((firstChar == 'w') || (firstChar == 'W')) && (secondChar == '/')))
+            {
+                // trailing whitespaces are removed by GetEntityTagLength()
+                var entityTagLength = EntityTagHeaderValue.GetEntityTagLength(input, current, out entityTag);
+
+                if (entityTagLength == 0)
+                {
+                    return 0;
+                }
+
+                current = current + entityTagLength;
+
+                // RangeConditionHeaderValue only allows 1 value. There must be no delimiter/other chars after an
+                // entity tag.
+                if (current != input.Length)
+                {
+                    return 0;
+                }
+            }
+            else
+            {
+                if (!HttpRuleParser.TryStringToDate(input.Subsegment(current), out date))
+                {
+                    return 0;
+                }
+
+                // If we got a valid date, then the parser consumed the whole string (incl. trailing whitespaces).
+                current = input.Length;
+            }
+
+            parsedValue = new RangeConditionHeaderValue();
+            if (entityTag == null)
+            {
+                parsedValue._lastModified = date;
+            }
+            else
+            {
+                parsedValue._entityTag = entityTag;
+            }
+
+            return current - startIndex;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Headers/src/RangeHeaderValue.cs b/src/Http/Headers/src/RangeHeaderValue.cs
new file mode 100644
index 0000000000000000000000000000000000000000..934b6b6cc17b0954d1e1f05a2acd9377fba0ef93
--- /dev/null
+++ b/src/Http/Headers/src/RangeHeaderValue.cs
@@ -0,0 +1,163 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Text;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class RangeHeaderValue
+    {
+        private static readonly HttpHeaderParser<RangeHeaderValue> Parser
+            = new GenericHeaderParser<RangeHeaderValue>(false, GetRangeLength);
+
+        private StringSegment _unit;
+        private ICollection<RangeItemHeaderValue> _ranges;
+
+        public RangeHeaderValue()
+        {
+            _unit = HeaderUtilities.BytesUnit;
+        }
+
+        public RangeHeaderValue(long? from, long? to)
+        {
+            // convenience ctor: "Range: bytes=from-to"
+            _unit = HeaderUtilities.BytesUnit;
+            Ranges.Add(new RangeItemHeaderValue(from, to));
+        }
+
+        public StringSegment Unit
+        {
+            get { return _unit; }
+            set
+            {
+                HeaderUtilities.CheckValidToken(value, nameof(value));
+                _unit = value;
+            }
+        }
+
+        public ICollection<RangeItemHeaderValue> Ranges
+        {
+            get
+            {
+                if (_ranges == null)
+                {
+                    _ranges = new ObjectCollection<RangeItemHeaderValue>();
+                }
+                return _ranges;
+            }
+        }
+
+        public override string ToString()
+        {
+            var sb = new StringBuilder();
+            sb.Append(_unit);
+            sb.Append('=');
+
+            var first = true;
+            foreach (var range in Ranges)
+            {
+                if (first)
+                {
+                    first = false;
+                }
+                else
+                {
+                    sb.Append(", ");
+                }
+
+                sb.Append(range.From);
+                sb.Append('-');
+                sb.Append(range.To);
+            }
+
+            return sb.ToString();
+        }
+
+        public override bool Equals(object obj)
+        {
+            var other = obj as RangeHeaderValue;
+
+            if (other == null)
+            {
+                return false;
+            }
+
+            return StringSegment.Equals(_unit, other._unit, StringComparison.OrdinalIgnoreCase) &&
+                HeaderUtilities.AreEqualCollections(Ranges, other.Ranges);
+        }
+
+        public override int GetHashCode()
+        {
+            var result = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_unit);
+
+            foreach (var range in Ranges)
+            {
+                result = result ^ range.GetHashCode();
+            }
+
+            return result;
+        }
+
+        public static RangeHeaderValue Parse(StringSegment input)
+        {
+            var index = 0;
+            return Parser.ParseValue(input, ref index);
+        }
+
+        public static bool TryParse(StringSegment input, out RangeHeaderValue parsedValue)
+        {
+            var index = 0;
+            return Parser.TryParseValue(input, ref index, out parsedValue);
+        }
+
+        private static int GetRangeLength(StringSegment input, int startIndex, out RangeHeaderValue parsedValue)
+        {
+            Contract.Requires(startIndex >= 0);
+
+            parsedValue = null;
+
+            if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+            {
+                return 0;
+            }
+
+            // Parse the unit string: <unit> in '<unit>=<from1>-<to1>, <from2>-<to2>'
+            var unitLength = HttpRuleParser.GetTokenLength(input, startIndex);
+
+            if (unitLength == 0)
+            {
+                return 0;
+            }
+
+            RangeHeaderValue result = new RangeHeaderValue();
+            result._unit = input.Subsegment(startIndex, unitLength);
+            var current = startIndex + unitLength;
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+            if ((current == input.Length) || (input[current] != '='))
+            {
+                return 0;
+            }
+
+            current++; // skip '=' separator
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+            var rangesLength = RangeItemHeaderValue.GetRangeItemListLength(input, current, result.Ranges);
+
+            if (rangesLength == 0)
+            {
+                return 0;
+            }
+
+            current = current + rangesLength;
+            Contract.Assert(current == input.Length, "GetRangeItemListLength() should consume the whole string or fail.");
+
+            parsedValue = result;
+            return current - startIndex;
+        }
+    }
+}
diff --git a/src/Http/Headers/src/RangeItemHeaderValue.cs b/src/Http/Headers/src/RangeItemHeaderValue.cs
new file mode 100644
index 0000000000000000000000000000000000000000..99fdbfef5c13e1307bde8d79d2a8aec4a4767616
--- /dev/null
+++ b/src/Http/Headers/src/RangeItemHeaderValue.cs
@@ -0,0 +1,229 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Globalization;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class RangeItemHeaderValue
+    {
+        private long? _from;
+        private long? _to;
+
+        public RangeItemHeaderValue(long? from, long? to)
+        {
+            if (!from.HasValue && !to.HasValue)
+            {
+                throw new ArgumentException("Invalid header range.");
+            }
+            if (from.HasValue && (from.Value < 0))
+            {
+                throw new ArgumentOutOfRangeException(nameof(from));
+            }
+            if (to.HasValue && (to.Value < 0))
+            {
+                throw new ArgumentOutOfRangeException(nameof(to));
+            }
+            if (from.HasValue && to.HasValue && (from.Value > to.Value))
+            {
+                throw new ArgumentOutOfRangeException(nameof(from));
+            }
+
+            _from = from;
+            _to = to;
+        }
+
+        public long? From
+        {
+            get { return _from; }
+        }
+
+        public long? To
+        {
+            get { return _to; }
+        }
+
+        public override string ToString()
+        {
+            if (!_from.HasValue)
+            {
+                return "-" + _to.Value.ToString(NumberFormatInfo.InvariantInfo);
+            }
+            else if (!_to.HasValue)
+            {
+                return _from.Value.ToString(NumberFormatInfo.InvariantInfo) + "-";
+            }
+            return _from.Value.ToString(NumberFormatInfo.InvariantInfo) + "-" +
+                _to.Value.ToString(NumberFormatInfo.InvariantInfo);
+        }
+
+        public override bool Equals(object obj)
+        {
+            var other = obj as RangeItemHeaderValue;
+
+            if (other == null)
+            {
+                return false;
+            }
+            return ((_from == other._from) && (_to == other._to));
+        }
+
+        public override int GetHashCode()
+        {
+            if (!_from.HasValue)
+            {
+                return _to.GetHashCode();
+            }
+            else if (!_to.HasValue)
+            {
+                return _from.GetHashCode();
+            }
+            return _from.GetHashCode() ^ _to.GetHashCode();
+        }
+
+        // Returns the length of a range list. E.g. "1-2, 3-4, 5-6" adds 3 ranges to 'rangeCollection'. Note that empty
+        // list segments are allowed, e.g. ",1-2, , 3-4,,".
+        internal static int GetRangeItemListLength(
+            StringSegment input,
+            int startIndex,
+            ICollection<RangeItemHeaderValue> rangeCollection)
+        {
+            Contract.Requires(rangeCollection != null);
+            Contract.Requires(startIndex >= 0);
+            Contract.Ensures((Contract.Result<int>() == 0) || (rangeCollection.Count > 0),
+                "If we can parse the string, then we expect to have at least one range item.");
+
+            if ((StringSegment.IsNullOrEmpty(input)) || (startIndex >= input.Length))
+            {
+                return 0;
+            }
+
+            // Empty segments are allowed, so skip all delimiter-only segments (e.g. ", ,").
+            var separatorFound = false;
+            var current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(input, startIndex, true, out separatorFound);
+            // It's OK if we didn't find leading separator characters. Ignore 'separatorFound'.
+
+            if (current == input.Length)
+            {
+                return 0;
+            }
+
+            RangeItemHeaderValue range = null;
+            while (true)
+            {
+                var rangeLength = GetRangeItemLength(input, current, out range);
+
+                if (rangeLength == 0)
+                {
+                    return 0;
+                }
+
+                rangeCollection.Add(range);
+
+                current = current + rangeLength;
+                current = HeaderUtilities.GetNextNonEmptyOrWhitespaceIndex(input, current, true, out separatorFound);
+
+                // If the string is not consumed, we must have a delimiter, otherwise the string is not a valid
+                // range list.
+                if ((current < input.Length) && !separatorFound)
+                {
+                    return 0;
+                }
+
+                if (current == input.Length)
+                {
+                    return current - startIndex;
+                }
+            }
+        }
+
+        internal static int GetRangeItemLength(StringSegment input, int startIndex, out RangeItemHeaderValue parsedValue)
+        {
+            Contract.Requires(startIndex >= 0);
+
+            // This parser parses number ranges: e.g. '1-2', '1-', '-2'.
+
+            parsedValue = null;
+
+            if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+            {
+                return 0;
+            }
+
+            // Caller must remove leading whitespaces. If not, we'll return 0.
+            var current = startIndex;
+
+            // Try parse the first value of a value pair.
+            var fromStartIndex = current;
+            var fromLength = HttpRuleParser.GetNumberLength(input, current, false);
+
+            if (fromLength > HttpRuleParser.MaxInt64Digits)
+            {
+                return 0;
+            }
+
+            current = current + fromLength;
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+            // Afer the first value, the '-' character must follow.
+            if ((current == input.Length) || (input[current] != '-'))
+            {
+                // We need a '-' character otherwise this can't be a valid range.
+                return 0;
+            }
+
+            current++; // skip the '-' character
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+            var toStartIndex = current;
+            var toLength = 0;
+
+            // If we didn't reach the end of the string, try parse the second value of the range.
+            if (current < input.Length)
+            {
+                toLength = HttpRuleParser.GetNumberLength(input, current, false);
+
+                if (toLength > HttpRuleParser.MaxInt64Digits)
+                {
+                    return 0;
+                }
+
+                current = current + toLength;
+                current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+            }
+
+            if ((fromLength == 0) && (toLength == 0))
+            {
+                return 0; // At least one value must be provided in order to be a valid range.
+            }
+
+            // Try convert first value to int64
+            long from = 0;
+            if ((fromLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Subsegment(fromStartIndex, fromLength), out from))
+            {
+                return 0;
+            }
+
+            // Try convert second value to int64
+            long to = 0;
+            if ((toLength > 0) && !HeaderUtilities.TryParseNonNegativeInt64(input.Subsegment(toStartIndex, toLength), out to))
+            {
+                return 0;
+            }
+
+            // 'from' must not be greater than 'to'
+            if ((fromLength > 0) && (toLength > 0) && (from > to))
+            {
+                return 0;
+            }
+
+            parsedValue = new RangeItemHeaderValue((fromLength == 0 ? (long?)null : (long?)from),
+                (toLength == 0 ? (long?)null : (long?)to));
+            return current - startIndex;
+        }
+    }
+}
diff --git a/src/Http/Headers/src/SameSiteMode.cs b/src/Http/Headers/src/SameSiteMode.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1976386c859468e69f85cdf80e7dc68eeecc8428
--- /dev/null
+++ b/src/Http/Headers/src/SameSiteMode.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.Net.Http.Headers
+{
+    // RFC Draft: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00
+    public enum SameSiteMode
+    {
+        None = 0,
+        Lax,
+        Strict
+    }
+}
diff --git a/src/Http/Headers/src/SetCookieHeaderValue.cs b/src/Http/Headers/src/SetCookieHeaderValue.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f3477648dee2cb106e2e2b9e5606c55f22994d42
--- /dev/null
+++ b/src/Http/Headers/src/SetCookieHeaderValue.cs
@@ -0,0 +1,523 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Text;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    // http://tools.ietf.org/html/rfc6265
+    public class SetCookieHeaderValue
+    {
+        private const string ExpiresToken = "expires";
+        private const string MaxAgeToken = "max-age";
+        private const string DomainToken = "domain";
+        private const string PathToken = "path";
+        private const string SecureToken = "secure";
+        // RFC Draft: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00
+        private const string SameSiteToken = "samesite";
+        private static readonly string SameSiteLaxToken = SameSiteMode.Lax.ToString().ToLower();
+        private static readonly string SameSiteStrictToken = SameSiteMode.Strict.ToString().ToLower();
+        private const string HttpOnlyToken = "httponly";
+        private const string SeparatorToken = "; ";
+        private const string EqualsToken = "=";
+        private const string DefaultPath = "/"; // TODO: Used?
+
+        private static readonly HttpHeaderParser<SetCookieHeaderValue> SingleValueParser
+            = new GenericHeaderParser<SetCookieHeaderValue>(false, GetSetCookieLength);
+        private static readonly HttpHeaderParser<SetCookieHeaderValue> MultipleValueParser
+            = new GenericHeaderParser<SetCookieHeaderValue>(true, GetSetCookieLength);
+
+        private StringSegment _name;
+        private StringSegment _value;
+
+        private SetCookieHeaderValue()
+        {
+            // Used by the parser to create a new instance of this type.
+        }
+
+        public SetCookieHeaderValue(StringSegment name)
+            : this(name, StringSegment.Empty)
+        {
+        }
+
+        public SetCookieHeaderValue(StringSegment name, StringSegment value)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            if (value == null)
+            {
+                throw new ArgumentNullException(nameof(value));
+            }
+
+            Name = name;
+            Value = value;
+        }
+
+        public StringSegment Name
+        {
+            get { return _name; }
+            set
+            {
+                CookieHeaderValue.CheckNameFormat(value, nameof(value));
+                _name = value;
+            }
+        }
+
+        public StringSegment Value
+        {
+            get { return _value; }
+            set
+            {
+                CookieHeaderValue.CheckValueFormat(value, nameof(value));
+                _value = value;
+            }
+        }
+
+        public DateTimeOffset? Expires { get; set; }
+
+        public TimeSpan? MaxAge { get; set; }
+
+        public StringSegment Domain { get; set; }
+
+        public StringSegment Path { get; set; }
+
+        public bool Secure { get; set; }
+
+        public SameSiteMode SameSite { get; set; }
+
+        public bool HttpOnly { get; set; }
+
+        // name="value"; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite={Strict|Lax}; httponly
+        public override string ToString()
+        {
+            var length = _name.Length + EqualsToken.Length + _value.Length;
+
+            string expires = null;
+            string maxAge = null;
+            string sameSite = null;
+
+            if (Expires.HasValue)
+            {
+                expires = HeaderUtilities.FormatDate(Expires.Value);
+                length += SeparatorToken.Length + ExpiresToken.Length + EqualsToken.Length + expires.Length;
+            }
+
+            if (MaxAge.HasValue)
+            {
+                maxAge = HeaderUtilities.FormatNonNegativeInt64((long)MaxAge.Value.TotalSeconds);
+                length += SeparatorToken.Length + MaxAgeToken.Length + EqualsToken.Length + maxAge.Length;
+            }
+
+            if (Domain != null)
+            {
+                length += SeparatorToken.Length + DomainToken.Length + EqualsToken.Length + Domain.Length;
+            }
+
+            if (Path != null)
+            {
+                length += SeparatorToken.Length + PathToken.Length + EqualsToken.Length + Path.Length;
+            }
+
+            if (Secure)
+            {
+                length += SeparatorToken.Length + SecureToken.Length;
+            }
+
+            if (SameSite != SameSiteMode.None)
+            {
+                sameSite = SameSite == SameSiteMode.Lax ? SameSiteLaxToken : SameSiteStrictToken;
+                length += SeparatorToken.Length + SameSiteToken.Length + EqualsToken.Length + sameSite.Length;
+            }
+
+            if (HttpOnly)
+            {
+                length += SeparatorToken.Length + HttpOnlyToken.Length;
+            }
+
+            var sb = new InplaceStringBuilder(length);
+
+            sb.Append(_name);
+            sb.Append(EqualsToken);
+            sb.Append(_value);
+
+            if (expires != null)
+            {
+                AppendSegment(ref sb, ExpiresToken, expires);
+            }
+
+            if (maxAge != null)
+            {
+                AppendSegment(ref sb, MaxAgeToken, maxAge);
+            }
+
+            if (Domain != null)
+            {
+                AppendSegment(ref sb, DomainToken, Domain);
+            }
+
+            if (Path != null)
+            {
+                AppendSegment(ref sb, PathToken, Path);
+            }
+
+            if (Secure)
+            {
+                AppendSegment(ref sb, SecureToken, null);
+            }
+
+            if (SameSite != SameSiteMode.None)
+            {
+                AppendSegment(ref sb, SameSiteToken, sameSite);
+            }
+
+            if (HttpOnly)
+            {
+                AppendSegment(ref sb, HttpOnlyToken, null);
+            }
+
+            return sb.ToString();
+        }
+
+        private static void AppendSegment(ref InplaceStringBuilder builder, StringSegment name, StringSegment value)
+        {
+            builder.Append(SeparatorToken);
+            builder.Append(name);
+            if (value != null)
+            {
+                builder.Append(EqualsToken);
+                builder.Append(value);
+            }
+        }
+
+        /// <summary>
+        /// Append string representation of this <see cref="SetCookieHeaderValue"/> to given
+        /// <paramref name="builder"/>.
+        /// </summary>
+        /// <param name="builder">
+        /// The <see cref="StringBuilder"/> to receive the string representation of this
+        /// <see cref="SetCookieHeaderValue"/>.
+        /// </param>
+        public void AppendToStringBuilder(StringBuilder builder)
+        {
+            builder.Append(_name);
+            builder.Append("=");
+            builder.Append(_value);
+
+            if (Expires.HasValue)
+            {
+                AppendSegment(builder, ExpiresToken, HeaderUtilities.FormatDate(Expires.Value));
+            }
+
+            if (MaxAge.HasValue)
+            {
+                AppendSegment(builder, MaxAgeToken, HeaderUtilities.FormatNonNegativeInt64((long)MaxAge.Value.TotalSeconds));
+            }
+
+            if (Domain != null)
+            {
+                AppendSegment(builder, DomainToken, Domain);
+            }
+
+            if (Path != null)
+            {
+                AppendSegment(builder, PathToken, Path);
+            }
+
+            if (Secure)
+            {
+                AppendSegment(builder, SecureToken, null);
+            }
+
+            if (SameSite != SameSiteMode.None)
+            {
+                AppendSegment(builder, SameSiteToken, SameSite == SameSiteMode.Lax ? SameSiteLaxToken : SameSiteStrictToken);
+            }
+
+            if (HttpOnly)
+            {
+                AppendSegment(builder, HttpOnlyToken, null);
+            }
+        }
+
+        private static void AppendSegment(StringBuilder builder, StringSegment name, StringSegment value)
+        {
+            builder.Append("; ");
+            builder.Append(name);
+            if (value != null)
+            {
+                builder.Append("=");
+                builder.Append(value);
+            }
+        }
+
+        public static SetCookieHeaderValue Parse(StringSegment input)
+        {
+            var index = 0;
+            return SingleValueParser.ParseValue(input, ref index);
+        }
+
+        public static bool TryParse(StringSegment input, out SetCookieHeaderValue parsedValue)
+        {
+            var index = 0;
+            return SingleValueParser.TryParseValue(input, ref index, out parsedValue);
+        }
+
+        public static IList<SetCookieHeaderValue> ParseList(IList<string> inputs)
+        {
+            return MultipleValueParser.ParseValues(inputs);
+        }
+
+        public static IList<SetCookieHeaderValue> ParseStrictList(IList<string> inputs)
+        {
+            return MultipleValueParser.ParseStrictValues(inputs);
+        }
+
+        public static bool TryParseList(IList<string> inputs, out IList<SetCookieHeaderValue> parsedValues)
+        {
+            return MultipleValueParser.TryParseValues(inputs, out parsedValues);
+        }
+
+        public static bool TryParseStrictList(IList<string> inputs, out IList<SetCookieHeaderValue> parsedValues)
+        {
+            return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
+        }
+
+        // name=value; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite={Strict|Lax}; httponly
+        private static int GetSetCookieLength(StringSegment input, int startIndex, out SetCookieHeaderValue parsedValue)
+        {
+            Contract.Requires(startIndex >= 0);
+            var offset = startIndex;
+
+            parsedValue = null;
+
+            if (StringSegment.IsNullOrEmpty(input) || (offset >= input.Length))
+            {
+                return 0;
+            }
+
+            var result = new SetCookieHeaderValue();
+
+            // The caller should have already consumed any leading whitespace, commas, etc..
+
+            // Name=value;
+
+            // Name
+            var itemLength = HttpRuleParser.GetTokenLength(input, offset);
+            if (itemLength == 0)
+            {
+                return 0;
+            }
+            result._name = input.Subsegment(offset, itemLength);
+            offset += itemLength;
+
+            // = (no spaces)
+            if (!ReadEqualsSign(input, ref offset))
+            {
+                return 0;
+            }
+
+            // value or "quoted value"
+            // The value may be empty
+            result._value = CookieHeaderValue.GetCookieValue(input, ref offset);
+
+            // *(';' SP cookie-av)
+            while (offset < input.Length)
+            {
+                if (input[offset] == ',')
+                {
+                    // Divider between headers
+                    break;
+                }
+                if (input[offset] != ';')
+                {
+                    // Expecting a ';' between parameters
+                    return 0;
+                }
+                offset++;
+
+                offset += HttpRuleParser.GetWhitespaceLength(input, offset);
+
+                //  cookie-av = expires-av / max-age-av / domain-av / path-av / secure-av / samesite-av / httponly-av / extension-av
+                itemLength = HttpRuleParser.GetTokenLength(input, offset);
+                if (itemLength == 0)
+                {
+                    // Trailing ';' or leading into garbage. Let the next parser fail.
+                    break;
+                }
+                var token = input.Subsegment(offset, itemLength);
+                offset += itemLength;
+
+                //  expires-av = "Expires=" sane-cookie-date
+                if (StringSegment.Equals(token, ExpiresToken, StringComparison.OrdinalIgnoreCase))
+                {
+                    // = (no spaces)
+                    if (!ReadEqualsSign(input, ref offset))
+                    {
+                        return 0;
+                    }
+                    var dateString = ReadToSemicolonOrEnd(input, ref offset);
+                    DateTimeOffset expirationDate;
+                    if (!HttpRuleParser.TryStringToDate(dateString, out expirationDate))
+                    {
+                        // Invalid expiration date, abort
+                        return 0;
+                    }
+                    result.Expires = expirationDate;
+                }
+                // max-age-av = "Max-Age=" non-zero-digit *DIGIT
+                else if (StringSegment.Equals(token, MaxAgeToken, StringComparison.OrdinalIgnoreCase))
+                {
+                    // = (no spaces)
+                    if (!ReadEqualsSign(input, ref offset))
+                    {
+                        return 0;
+                    }
+
+                    itemLength = HttpRuleParser.GetNumberLength(input, offset, allowDecimal: false);
+                    if (itemLength == 0)
+                    {
+                        return 0;
+                    }
+                    var numberString = input.Subsegment(offset, itemLength);
+                    long maxAge;
+                    if (!HeaderUtilities.TryParseNonNegativeInt64(numberString, out maxAge))
+                    {
+                        // Invalid expiration date, abort
+                        return 0;
+                    }
+                    result.MaxAge = TimeSpan.FromSeconds(maxAge);
+                    offset += itemLength;
+                }
+                // domain-av = "Domain=" domain-value
+                // domain-value = <subdomain> ; defined in [RFC1034], Section 3.5, as enhanced by [RFC1123], Section 2.1
+                else if (StringSegment.Equals(token, DomainToken, StringComparison.OrdinalIgnoreCase))
+                {
+                    // = (no spaces)
+                    if (!ReadEqualsSign(input, ref offset))
+                    {
+                        return 0;
+                    }
+                    // We don't do any detailed validation on the domain.
+                    result.Domain = ReadToSemicolonOrEnd(input, ref offset);
+                }
+                // path-av = "Path=" path-value
+                // path-value = <any CHAR except CTLs or ";">
+                else if (StringSegment.Equals(token, PathToken, StringComparison.OrdinalIgnoreCase))
+                {
+                    // = (no spaces)
+                    if (!ReadEqualsSign(input, ref offset))
+                    {
+                        return 0;
+                    }
+                    // We don't do any detailed validation on the path.
+                    result.Path = ReadToSemicolonOrEnd(input, ref offset);
+                }
+                // secure-av = "Secure"
+                else if (StringSegment.Equals(token, SecureToken, StringComparison.OrdinalIgnoreCase))
+                {
+                    result.Secure = true;
+                }
+                // samesite-av = "SameSite" / "SameSite=" samesite-value
+                // samesite-value = "Strict" / "Lax"
+                else if (StringSegment.Equals(token, SameSiteToken, StringComparison.OrdinalIgnoreCase))
+                {
+                    if (!ReadEqualsSign(input, ref offset))
+                    {
+                        result.SameSite = SameSiteMode.Strict;
+                    }
+                    else
+                    {
+                        var enforcementMode = ReadToSemicolonOrEnd(input, ref offset);
+
+                        if (StringSegment.Equals(enforcementMode, SameSiteLaxToken, StringComparison.OrdinalIgnoreCase))
+                        {
+                            result.SameSite = SameSiteMode.Lax;
+                        }
+                        else
+                        {
+                            result.SameSite = SameSiteMode.Strict;
+                        }
+                    }
+                }
+                // httponly-av = "HttpOnly"
+                else if (StringSegment.Equals(token, HttpOnlyToken, StringComparison.OrdinalIgnoreCase))
+                {
+                    result.HttpOnly = true;
+                }
+                // extension-av = <any CHAR except CTLs or ";">
+                else
+                {
+                    // TODO: skip it? Store it in a list?
+                }
+            }
+
+            parsedValue = result;
+            return offset - startIndex;
+        }
+
+        private static bool ReadEqualsSign(StringSegment input, ref int offset)
+        {
+            // = (no spaces)
+            if (offset >= input.Length || input[offset] != '=')
+            {
+                return false;
+            }
+            offset++;
+            return true;
+        }
+
+        private static StringSegment ReadToSemicolonOrEnd(StringSegment input, ref int offset)
+        {
+            var end = input.IndexOf(';', offset);
+            if (end < 0)
+            {
+                // Remainder of the string
+                end = input.Length;
+            }
+            var itemLength = end - offset;
+            var result = input.Subsegment(offset, itemLength);
+            offset += itemLength;
+            return result;
+        }
+
+        public override bool Equals(object obj)
+        {
+            var other = obj as SetCookieHeaderValue;
+
+            if (other == null)
+            {
+                return false;
+            }
+
+            return StringSegment.Equals(_name, other._name, StringComparison.OrdinalIgnoreCase)
+                && StringSegment.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase)
+                && Expires.Equals(other.Expires)
+                && MaxAge.Equals(other.MaxAge)
+                && StringSegment.Equals(Domain, other.Domain, StringComparison.OrdinalIgnoreCase)
+                && StringSegment.Equals(Path, other.Path, StringComparison.OrdinalIgnoreCase)
+                && Secure == other.Secure
+                && SameSite == other.SameSite
+                && HttpOnly == other.HttpOnly;
+        }
+
+        public override int GetHashCode()
+        {
+            return StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_name)
+                ^ StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_value)
+                ^ (Expires.HasValue ? Expires.GetHashCode() : 0)
+                ^ (MaxAge.HasValue ? MaxAge.GetHashCode() : 0)
+                ^ (Domain != null ? StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(Domain) : 0)
+                ^ (Path != null ? StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(Path) : 0)
+                ^ Secure.GetHashCode()
+                ^ SameSite.GetHashCode()
+                ^ HttpOnly.GetHashCode();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Headers/src/StringWithQualityHeaderValue.cs b/src/Http/Headers/src/StringWithQualityHeaderValue.cs
new file mode 100644
index 0000000000000000000000000000000000000000..deba2d26975e61b32f02771629cf2b4e131e596a
--- /dev/null
+++ b/src/Http/Headers/src/StringWithQualityHeaderValue.cs
@@ -0,0 +1,222 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.Contracts;
+using System.Globalization;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class StringWithQualityHeaderValue
+    {
+        private static readonly HttpHeaderParser<StringWithQualityHeaderValue> SingleValueParser
+            = new GenericHeaderParser<StringWithQualityHeaderValue>(false, GetStringWithQualityLength);
+        private static readonly HttpHeaderParser<StringWithQualityHeaderValue> MultipleValueParser
+            = new GenericHeaderParser<StringWithQualityHeaderValue>(true, GetStringWithQualityLength);
+
+        private StringSegment _value;
+        private double? _quality;
+
+        private StringWithQualityHeaderValue()
+        {
+            // Used by the parser to create a new instance of this type.
+        }
+
+        public StringWithQualityHeaderValue(StringSegment value)
+        {
+            HeaderUtilities.CheckValidToken(value, nameof(value));
+
+            _value = value;
+        }
+
+        public StringWithQualityHeaderValue(StringSegment value, double quality)
+        {
+            HeaderUtilities.CheckValidToken(value, nameof(value));
+
+            if ((quality < 0) || (quality > 1))
+            {
+                throw new ArgumentOutOfRangeException(nameof(quality));
+            }
+
+            _value = value;
+            _quality = quality;
+        }
+
+        public StringSegment Value
+        {
+            get { return _value; }
+        }
+
+        public double? Quality
+        {
+            get { return _quality; }
+        }
+
+        public override string ToString()
+        {
+            if (_quality.HasValue)
+            {
+                return _value + "; q=" + _quality.Value.ToString("0.0##", NumberFormatInfo.InvariantInfo);
+            }
+
+            return _value.ToString();
+        }
+
+        public override bool Equals(object obj)
+        {
+            var other = obj as StringWithQualityHeaderValue;
+
+            if (other == null)
+            {
+                return false;
+            }
+
+            if (!StringSegment.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase))
+            {
+                return false;
+            }
+
+            if (_quality.HasValue)
+            {
+                // Note that we don't consider double.Epsilon here. We really consider two values equal if they're
+                // actually equal. This makes sure that we also get the same hashcode for two values considered equal
+                // by Equals().
+                return other._quality.HasValue && (_quality.Value == other._quality.Value);
+            }
+
+            // If we don't have a quality value, then 'other' must also have no quality assigned in order to be
+            // considered equal.
+            return !other._quality.HasValue;
+        }
+
+        public override int GetHashCode()
+        {
+            var result = StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_value);
+
+            if (_quality.HasValue)
+            {
+                result = result ^ _quality.Value.GetHashCode();
+            }
+
+            return result;
+        }
+
+        public static StringWithQualityHeaderValue Parse(StringSegment input)
+        {
+            var index = 0;
+            return SingleValueParser.ParseValue(input, ref index);
+        }
+
+        public static bool TryParse(StringSegment input, out StringWithQualityHeaderValue parsedValue)
+        {
+            var index = 0;
+            return SingleValueParser.TryParseValue(input, ref index, out parsedValue);
+        }
+
+        public static IList<StringWithQualityHeaderValue> ParseList(IList<string> input)
+        {
+            return MultipleValueParser.ParseValues(input);
+        }
+
+        public static IList<StringWithQualityHeaderValue> ParseStrictList(IList<string> input)
+        {
+            return MultipleValueParser.ParseStrictValues(input);
+        }
+
+        public static bool TryParseList(IList<string> input, out IList<StringWithQualityHeaderValue> parsedValues)
+        {
+            return MultipleValueParser.TryParseValues(input, out parsedValues);
+        }
+
+        public static bool TryParseStrictList(IList<string> input, out IList<StringWithQualityHeaderValue> parsedValues)
+        {
+            return MultipleValueParser.TryParseStrictValues(input, out parsedValues);
+        }
+
+        private static int GetStringWithQualityLength(StringSegment input, int startIndex, out StringWithQualityHeaderValue parsedValue)
+        {
+            Contract.Requires(startIndex >= 0);
+
+            parsedValue = null;
+
+            if (StringSegment.IsNullOrEmpty(input) || (startIndex >= input.Length))
+            {
+                return 0;
+            }
+
+            // Parse the value string: <value> in '<value>; q=<quality>'
+            var valueLength = HttpRuleParser.GetTokenLength(input, startIndex);
+
+            if (valueLength == 0)
+            {
+                return 0;
+            }
+
+            StringWithQualityHeaderValue result = new StringWithQualityHeaderValue();
+            result._value = input.Subsegment(startIndex, valueLength);
+            var current = startIndex + valueLength;
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+            if ((current == input.Length) || (input[current] != ';'))
+            {
+                parsedValue = result;
+                return current - startIndex; // we have a valid token, but no quality.
+            }
+
+            current++; // skip ';' separator
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+            // If we found a ';' separator, it must be followed by a quality information
+            if (!TryReadQuality(input, result, ref current))
+            {
+                return 0;
+            }
+
+            parsedValue = result;
+            return current - startIndex;
+        }
+
+        private static bool TryReadQuality(StringSegment input, StringWithQualityHeaderValue result, ref int index)
+        {
+            var current = index;
+
+            // See if we have a quality value by looking for "q"
+            if ((current == input.Length) || ((input[current] != 'q') && (input[current] != 'Q')))
+            {
+                return false;
+            }
+
+            current++; // skip 'q' identifier
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+            // If we found "q" it must be followed by "="
+            if ((current == input.Length) || (input[current] != '='))
+            {
+                return false;
+            }
+
+            current++; // skip '=' separator
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+            if (current == input.Length)
+            {
+                return false;
+            }
+
+            if (!HeaderUtilities.TryParseQualityDouble(input, current, out var quality, out var qualityLength))
+            {
+                return false;
+            }
+
+            result._quality = quality;
+
+            current = current + qualityLength;
+            current = current + HttpRuleParser.GetWhitespaceLength(input, current);
+
+            index = current;
+            return true;
+        }
+    }
+}
diff --git a/src/Http/Headers/src/StringWithQualityHeaderValueComparer.cs b/src/Http/Headers/src/StringWithQualityHeaderValueComparer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..961cc078417bff23d70f3a7f97439d2ef3604ec4
--- /dev/null
+++ b/src/Http/Headers/src/StringWithQualityHeaderValueComparer.cs
@@ -0,0 +1,83 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.Net.Http.Headers
+{
+    /// <summary>
+    /// Implementation of <see cref="IComparer{T}"/> that can compare content negotiation header fields
+    /// based on their quality values (a.k.a q-values). This applies to values used in accept-charset,
+    /// accept-encoding, accept-language and related header fields with similar syntax rules. See
+    /// <see cref="MediaTypeHeaderValueComparer"/> for a comparer for media type
+    /// q-values.
+    /// </summary>
+    public class StringWithQualityHeaderValueComparer : IComparer<StringWithQualityHeaderValue>
+    {
+        private static readonly StringWithQualityHeaderValueComparer _qualityComparer =
+            new StringWithQualityHeaderValueComparer();
+
+        private StringWithQualityHeaderValueComparer()
+        {
+        }
+
+        public static StringWithQualityHeaderValueComparer QualityComparer
+        {
+            get { return _qualityComparer; }
+        }
+
+        /// <summary>
+        /// Compares two <see cref="StringWithQualityHeaderValue"/> based on their quality value
+        /// (a.k.a their "q-value").
+        /// Values with identical q-values are considered equal (i.e the result is 0) with the exception of wild-card
+        /// values (i.e. a value of "*") which are considered less than non-wild-card values. This allows to sort
+        /// a sequence of <see cref="StringWithQualityHeaderValue"/> following their q-values ending up with any
+        /// wild-cards at the end.
+        /// </summary>
+        /// <param name="stringWithQuality1">The first value to compare.</param>
+        /// <param name="stringWithQuality2">The second value to compare</param>
+        /// <returns>The result of the comparison.</returns>
+        public int Compare(
+            StringWithQualityHeaderValue stringWithQuality1,
+            StringWithQualityHeaderValue stringWithQuality2)
+        {
+            if (stringWithQuality1 == null)
+            {
+                throw new ArgumentNullException(nameof(stringWithQuality1));
+            }
+
+            if (stringWithQuality2 == null)
+            {
+                throw new ArgumentNullException(nameof(stringWithQuality2));
+            }
+
+            var quality1 = stringWithQuality1.Quality ?? HeaderQuality.Match;
+            var quality2 = stringWithQuality2.Quality ?? HeaderQuality.Match;
+            var qualityDifference = quality1 - quality2;
+            if (qualityDifference < 0)
+            {
+                return -1;
+            }
+            else if (qualityDifference > 0)
+            {
+                return 1;
+            }
+
+            if (!StringSegment.Equals(stringWithQuality1.Value, stringWithQuality2.Value, StringComparison.OrdinalIgnoreCase))
+            {
+                if (StringSegment.Equals(stringWithQuality1.Value, "*", StringComparison.Ordinal))
+                {
+                    return -1;
+                }
+                else if (StringSegment.Equals(stringWithQuality2.Value, "*", StringComparison.Ordinal))
+                {
+                    return 1;
+                }
+            }
+
+            return 0;
+        }
+    }
+}
diff --git a/src/Http/Headers/src/baseline.netcore.json b/src/Http/Headers/src/baseline.netcore.json
new file mode 100644
index 0000000000000000000000000000000000000000..476f8150a73c576a60f8f9ec1f6c05727c7188f7
--- /dev/null
+++ b/src/Http/Headers/src/baseline.netcore.json
@@ -0,0 +1,4110 @@
+{
+  "AssemblyIdentity": "Microsoft.Net.Http.Headers, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+  "Types": [
+    {
+      "Name": "Microsoft.Net.Http.Headers.CacheControlHeaderValue",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_NoCache",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_NoCache",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_NoCacheHeaders",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.ICollection<Microsoft.Extensions.Primitives.StringSegment>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_NoStore",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_NoStore",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MaxAge",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.TimeSpan>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_MaxAge",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.TimeSpan>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_SharedMaxAge",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.TimeSpan>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_SharedMaxAge",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.TimeSpan>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MaxStale",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_MaxStale",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MaxStaleLimit",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.TimeSpan>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_MaxStaleLimit",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.TimeSpan>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MinFresh",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.TimeSpan>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_MinFresh",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.TimeSpan>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_NoTransform",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_NoTransform",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_OnlyIfCached",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_OnlyIfCached",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Public",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Public",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Private",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Private",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_PrivateHeaders",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.ICollection<Microsoft.Extensions.Primitives.StringSegment>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MustRevalidate",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_MustRevalidate",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ProxyRevalidate",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ProxyRevalidate",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Extensions",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.NameValueHeaderValue>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "obj",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHashCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Parse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "Microsoft.Net.Http.Headers.CacheControlHeaderValue",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "parsedValue",
+              "Type": "Microsoft.Net.Http.Headers.CacheControlHeaderValue",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "PublicString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "PrivateString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "MaxAgeString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "SharedMaxAgeString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "NoCacheString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "NoStoreString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "MaxStaleString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "MinFreshString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "NoTransformString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "OnlyIfCachedString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "MustRevalidateString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "ProxyRevalidateString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.ContentDispositionHeaderValue",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_DispositionType",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_DispositionType",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Parameters",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.NameValueHeaderValue>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Name",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Name",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_FileName",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_FileName",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_FileNameStar",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_FileNameStar",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_CreationDate",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_CreationDate",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.DateTimeOffset>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ModificationDate",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ModificationDate",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.DateTimeOffset>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ReadDate",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ReadDate",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.DateTimeOffset>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Size",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Int64>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Size",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.Int64>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SetHttpFileName",
+          "Parameters": [
+            {
+              "Name": "fileName",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SetMimeFileName",
+          "Parameters": [
+            {
+              "Name": "fileName",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "obj",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHashCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Parse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "Microsoft.Net.Http.Headers.ContentDispositionHeaderValue",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "parsedValue",
+              "Type": "Microsoft.Net.Http.Headers.ContentDispositionHeaderValue",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "dispositionType",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.ContentDispositionHeaderValueIdentityExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "IsFileDisposition",
+          "Parameters": [
+            {
+              "Name": "header",
+              "Type": "Microsoft.Net.Http.Headers.ContentDispositionHeaderValue"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "IsFormDisposition",
+          "Parameters": [
+            {
+              "Name": "header",
+              "Type": "Microsoft.Net.Http.Headers.ContentDispositionHeaderValue"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.ContentRangeHeaderValue",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Unit",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Unit",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_From",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Int64>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_To",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Int64>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Length",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Int64>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HasLength",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HasRange",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "obj",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHashCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Parse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "Microsoft.Net.Http.Headers.ContentRangeHeaderValue",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "parsedValue",
+              "Type": "Microsoft.Net.Http.Headers.ContentRangeHeaderValue",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "from",
+              "Type": "System.Int64"
+            },
+            {
+              "Name": "to",
+              "Type": "System.Int64"
+            },
+            {
+              "Name": "length",
+              "Type": "System.Int64"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "length",
+              "Type": "System.Int64"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "from",
+              "Type": "System.Int64"
+            },
+            {
+              "Name": "to",
+              "Type": "System.Int64"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.CookieHeaderValue",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Name",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Name",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Value",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Value",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Parse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "Microsoft.Net.Http.Headers.CookieHeaderValue",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "parsedValue",
+              "Type": "Microsoft.Net.Http.Headers.CookieHeaderValue",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ParseList",
+          "Parameters": [
+            {
+              "Name": "inputs",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.CookieHeaderValue>",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ParseStrictList",
+          "Parameters": [
+            {
+              "Name": "inputs",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.CookieHeaderValue>",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParseList",
+          "Parameters": [
+            {
+              "Name": "inputs",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            },
+            {
+              "Name": "parsedValues",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.CookieHeaderValue>",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParseStrictList",
+          "Parameters": [
+            {
+              "Name": "inputs",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            },
+            {
+              "Name": "parsedValues",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.CookieHeaderValue>",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "obj",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHashCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.EntityTagHeaderValue",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Any",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.EntityTagHeaderValue",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Tag",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IsWeak",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "obj",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHashCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Compare",
+          "Parameters": [
+            {
+              "Name": "other",
+              "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue"
+            },
+            {
+              "Name": "useStrongComparison",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Parse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "Microsoft.Net.Http.Headers.EntityTagHeaderValue",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "parsedValue",
+              "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ParseList",
+          "Parameters": [
+            {
+              "Name": "inputs",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.EntityTagHeaderValue>",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ParseStrictList",
+          "Parameters": [
+            {
+              "Name": "inputs",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.EntityTagHeaderValue>",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParseList",
+          "Parameters": [
+            {
+              "Name": "inputs",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            },
+            {
+              "Name": "parsedValues",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.EntityTagHeaderValue>",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParseStrictList",
+          "Parameters": [
+            {
+              "Name": "inputs",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            },
+            {
+              "Name": "parsedValues",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.EntityTagHeaderValue>",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "tag",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "tag",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "isWeak",
+              "Type": "System.Boolean"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.HeaderNames",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Field",
+          "Name": "Accept",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Accept\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "AcceptCharset",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Accept-Charset\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "AcceptEncoding",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Accept-Encoding\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "AcceptLanguage",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Accept-Language\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "AcceptRanges",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Accept-Ranges\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "AccessControlAllowCredentials",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Access-Control-Allow-Credentials\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "AccessControlAllowHeaders",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Access-Control-Allow-Headers\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "AccessControlAllowMethods",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Access-Control-Allow-Methods\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "AccessControlAllowOrigin",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Access-Control-Allow-Origin\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "AccessControlExposeHeaders",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Access-Control-Expose-Headers\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "AccessControlMaxAge",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Access-Control-Max-Age\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "AccessControlRequestHeaders",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Access-Control-Request-Headers\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "AccessControlRequestMethod",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Access-Control-Request-Method\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Age",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Age\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Allow",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Allow\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Authority",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\":authority\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Authorization",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Authorization\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "CacheControl",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Cache-Control\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Connection",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Connection\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "ContentDisposition",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Content-Disposition\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "ContentEncoding",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Content-Encoding\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "ContentLanguage",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Content-Language\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "ContentLength",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Content-Length\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "ContentLocation",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Content-Location\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "ContentMD5",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Content-MD5\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "ContentRange",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Content-Range\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "ContentSecurityPolicy",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Content-Security-Policy\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "ContentSecurityPolicyReportOnly",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Content-Security-Policy-Report-Only\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "ContentType",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Content-Type\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Cookie",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Cookie\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Date",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Date\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "ETag",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"ETag\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Expires",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Expires\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Expect",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Expect\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "From",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"From\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Host",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Host\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "IfMatch",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"If-Match\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "IfModifiedSince",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"If-Modified-Since\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "IfNoneMatch",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"If-None-Match\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "IfRange",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"If-Range\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "IfUnmodifiedSince",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"If-Unmodified-Since\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "LastModified",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Last-Modified\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Location",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Location\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "MaxForwards",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Max-Forwards\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Method",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\":method\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Origin",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Origin\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Path",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\":path\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Pragma",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Pragma\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "ProxyAuthenticate",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Proxy-Authenticate\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "ProxyAuthorization",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Proxy-Authorization\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Range",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Range\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Referer",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Referer\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "RetryAfter",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Retry-After\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Scheme",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\":scheme\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Server",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Server\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "SetCookie",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Set-Cookie\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\":status\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "StrictTransportSecurity",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Strict-Transport-Security\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "TE",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"TE\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Trailer",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Trailer\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "TransferEncoding",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Transfer-Encoding\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Upgrade",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Upgrade\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "UserAgent",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"User-Agent\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Vary",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Vary\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Via",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Via\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "Warning",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Warning\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "WebSocketSubProtocols",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Sec-WebSocket-Protocol\""
+        },
+        {
+          "Kind": "Field",
+          "Name": "WWWAuthenticate",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"WWW-Authenticate\""
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.HeaderQuality",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Field",
+          "Name": "Match",
+          "Parameters": [],
+          "ReturnType": "System.Double",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "1"
+        },
+        {
+          "Kind": "Field",
+          "Name": "NoMatch",
+          "Parameters": [],
+          "ReturnType": "System.Double",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "0"
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.HeaderUtilities",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "TryParseSeconds",
+          "Parameters": [
+            {
+              "Name": "headerValues",
+              "Type": "Microsoft.Extensions.Primitives.StringValues"
+            },
+            {
+              "Name": "targetValue",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.TimeSpan>",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ContainsCacheDirective",
+          "Parameters": [
+            {
+              "Name": "cacheControlDirectives",
+              "Type": "Microsoft.Extensions.Primitives.StringValues"
+            },
+            {
+              "Name": "targetDirectives",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParseNonNegativeInt32",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "result",
+              "Type": "System.Int32",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParseNonNegativeInt64",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "result",
+              "Type": "System.Int64",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "FormatNonNegativeInt64",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int64"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParseDate",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "result",
+              "Type": "System.DateTimeOffset",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "FormatDate",
+          "Parameters": [
+            {
+              "Name": "dateTime",
+              "Type": "System.DateTimeOffset"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "FormatDate",
+          "Parameters": [
+            {
+              "Name": "dateTime",
+              "Type": "System.DateTimeOffset"
+            },
+            {
+              "Name": "quoted",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "RemoveQuotes",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "IsQuoted",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UnescapeAsQuotedString",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "EscapeAsQuotedString",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Charset",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Charset",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Encoding",
+          "Parameters": [],
+          "ReturnType": "System.Text.Encoding",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Encoding",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Text.Encoding"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Boundary",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Boundary",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Parameters",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.NameValueHeaderValue>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Quality",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Double>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Quality",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.Double>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MediaType",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_MediaType",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Type",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_SubType",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_SubTypeWithoutSuffix",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Suffix",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Facets",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IEnumerable<Microsoft.Extensions.Primitives.StringSegment>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MatchesAllTypes",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MatchesAllSubTypes",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MatchesAllSubTypesWithoutSuffix",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IsReadOnly",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "IsSubsetOf",
+          "Parameters": [
+            {
+              "Name": "otherMediaType",
+              "Type": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Copy",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "CopyAsReadOnly",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "obj",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHashCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Parse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "parsedValue",
+              "Type": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ParseList",
+          "Parameters": [
+            {
+              "Name": "inputs",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.MediaTypeHeaderValue>",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ParseStrictList",
+          "Parameters": [
+            {
+              "Name": "inputs",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.MediaTypeHeaderValue>",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParseList",
+          "Parameters": [
+            {
+              "Name": "inputs",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            },
+            {
+              "Name": "parsedValues",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.MediaTypeHeaderValue>",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParseStrictList",
+          "Parameters": [
+            {
+              "Name": "inputs",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            },
+            {
+              "Name": "parsedValues",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.MediaTypeHeaderValue>",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "mediaType",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "mediaType",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "quality",
+              "Type": "System.Double"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.MediaTypeHeaderValueComparer",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "System.Collections.Generic.IComparer<Microsoft.Net.Http.Headers.MediaTypeHeaderValue>"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_QualityComparer",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.MediaTypeHeaderValueComparer",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Compare",
+          "Parameters": [
+            {
+              "Name": "mediaType1",
+              "Type": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue"
+            },
+            {
+              "Name": "mediaType2",
+              "Type": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue"
+            }
+          ],
+          "ReturnType": "System.Int32",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.IComparer<Microsoft.Net.Http.Headers.MediaTypeHeaderValue>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.NameValueHeaderValue",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Name",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Value",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Value",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IsReadOnly",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Copy",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.NameValueHeaderValue",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "CopyAsReadOnly",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.NameValueHeaderValue",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHashCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "obj",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetUnescapedValue",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SetAndEscapeValue",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Parse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "Microsoft.Net.Http.Headers.NameValueHeaderValue",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "parsedValue",
+              "Type": "Microsoft.Net.Http.Headers.NameValueHeaderValue",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ParseList",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.NameValueHeaderValue>",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ParseStrictList",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.NameValueHeaderValue>",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParseList",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            },
+            {
+              "Name": "parsedValues",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.NameValueHeaderValue>",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParseStrictList",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            },
+            {
+              "Name": "parsedValues",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.NameValueHeaderValue>",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Find",
+          "Parameters": [
+            {
+              "Name": "values",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.NameValueHeaderValue>"
+            },
+            {
+              "Name": "name",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "Microsoft.Net.Http.Headers.NameValueHeaderValue",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.RangeConditionHeaderValue",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_LastModified",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_EntityTag",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.EntityTagHeaderValue",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "obj",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHashCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Parse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "Microsoft.Net.Http.Headers.RangeConditionHeaderValue",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "parsedValue",
+              "Type": "Microsoft.Net.Http.Headers.RangeConditionHeaderValue",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "lastModified",
+              "Type": "System.DateTimeOffset"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "entityTag",
+              "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "entityTag",
+              "Type": "System.String"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.RangeHeaderValue",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Unit",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Unit",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Ranges",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.ICollection<Microsoft.Net.Http.Headers.RangeItemHeaderValue>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "obj",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHashCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Parse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "Microsoft.Net.Http.Headers.RangeHeaderValue",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "parsedValue",
+              "Type": "Microsoft.Net.Http.Headers.RangeHeaderValue",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "from",
+              "Type": "System.Nullable<System.Int64>"
+            },
+            {
+              "Name": "to",
+              "Type": "System.Nullable<System.Int64>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.RangeItemHeaderValue",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_From",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Int64>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_To",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Int64>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "obj",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHashCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "from",
+              "Type": "System.Nullable<System.Int64>"
+            },
+            {
+              "Name": "to",
+              "Type": "System.Nullable<System.Int64>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.SameSiteMode",
+      "Visibility": "Public",
+      "Kind": "Enumeration",
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Field",
+          "Name": "None",
+          "Parameters": [],
+          "GenericParameter": [],
+          "Literal": "0"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Lax",
+          "Parameters": [],
+          "GenericParameter": [],
+          "Literal": "1"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Strict",
+          "Parameters": [],
+          "GenericParameter": [],
+          "Literal": "2"
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.SetCookieHeaderValue",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Name",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Name",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Value",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Value",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Expires",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Expires",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.DateTimeOffset>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MaxAge",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.TimeSpan>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_MaxAge",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.TimeSpan>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Domain",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Domain",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Path",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Path",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Secure",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Secure",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_SameSite",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.SameSiteMode",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_SameSite",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Net.Http.Headers.SameSiteMode"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HttpOnly",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_HttpOnly",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AppendToStringBuilder",
+          "Parameters": [
+            {
+              "Name": "builder",
+              "Type": "System.Text.StringBuilder"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Parse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "Microsoft.Net.Http.Headers.SetCookieHeaderValue",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "parsedValue",
+              "Type": "Microsoft.Net.Http.Headers.SetCookieHeaderValue",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ParseList",
+          "Parameters": [
+            {
+              "Name": "inputs",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.SetCookieHeaderValue>",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ParseStrictList",
+          "Parameters": [
+            {
+              "Name": "inputs",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.SetCookieHeaderValue>",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParseList",
+          "Parameters": [
+            {
+              "Name": "inputs",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            },
+            {
+              "Name": "parsedValues",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.SetCookieHeaderValue>",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParseStrictList",
+          "Parameters": [
+            {
+              "Name": "inputs",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            },
+            {
+              "Name": "parsedValues",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.SetCookieHeaderValue>",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "obj",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHashCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.StringWithQualityHeaderValue",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Value",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringSegment",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Quality",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Double>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "obj",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHashCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Parse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "ReturnType": "Microsoft.Net.Http.Headers.StringWithQualityHeaderValue",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParse",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "parsedValue",
+              "Type": "Microsoft.Net.Http.Headers.StringWithQualityHeaderValue",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ParseList",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.StringWithQualityHeaderValue>",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ParseStrictList",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.StringWithQualityHeaderValue>",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParseList",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            },
+            {
+              "Name": "parsedValues",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.StringWithQualityHeaderValue>",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryParseStrictList",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "System.Collections.Generic.IList<System.String>"
+            },
+            {
+              "Name": "parsedValues",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.StringWithQualityHeaderValue>",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "quality",
+              "Type": "System.Double"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Net.Http.Headers.StringWithQualityHeaderValueComparer",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "System.Collections.Generic.IComparer<Microsoft.Net.Http.Headers.StringWithQualityHeaderValue>"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_QualityComparer",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.StringWithQualityHeaderValueComparer",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Compare",
+          "Parameters": [
+            {
+              "Name": "stringWithQuality1",
+              "Type": "Microsoft.Net.Http.Headers.StringWithQualityHeaderValue"
+            },
+            {
+              "Name": "stringWithQuality2",
+              "Type": "Microsoft.Net.Http.Headers.StringWithQualityHeaderValue"
+            }
+          ],
+          "ReturnType": "System.Int32",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.IComparer<Microsoft.Net.Http.Headers.StringWithQualityHeaderValue>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/Http/Headers/test/CacheControlHeaderValueTest.cs b/src/Http/Headers/test/CacheControlHeaderValueTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..51e8ce5f5805e6b8389c7efc74e9b32b7d56affc
--- /dev/null
+++ b/src/Http/Headers/test/CacheControlHeaderValueTest.cs
@@ -0,0 +1,599 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using Xunit;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class CacheControlHeaderValueTest
+    {
+        [Fact]
+        public void Properties_SetAndGetAllProperties_SetValueReturnedInGetter()
+        {
+            var cacheControl = new CacheControlHeaderValue();
+
+            // Bool properties
+            cacheControl.NoCache = true;
+            Assert.True(cacheControl.NoCache, "NoCache");
+            cacheControl.NoStore = true;
+            Assert.True(cacheControl.NoStore, "NoStore");
+            cacheControl.MaxStale = true;
+            Assert.True(cacheControl.MaxStale, "MaxStale");
+            cacheControl.NoTransform = true;
+            Assert.True(cacheControl.NoTransform, "NoTransform");
+            cacheControl.OnlyIfCached = true;
+            Assert.True(cacheControl.OnlyIfCached, "OnlyIfCached");
+            cacheControl.Public = true;
+            Assert.True(cacheControl.Public, "Public");
+            cacheControl.Private = true;
+            Assert.True(cacheControl.Private, "Private");
+            cacheControl.MustRevalidate = true;
+            Assert.True(cacheControl.MustRevalidate, "MustRevalidate");
+            cacheControl.ProxyRevalidate = true;
+            Assert.True(cacheControl.ProxyRevalidate, "ProxyRevalidate");
+
+            // TimeSpan properties
+            TimeSpan timeSpan = new TimeSpan(1, 2, 3);
+            cacheControl.MaxAge = timeSpan;
+            Assert.Equal(timeSpan, cacheControl.MaxAge);
+            cacheControl.SharedMaxAge = timeSpan;
+            Assert.Equal(timeSpan, cacheControl.SharedMaxAge);
+            cacheControl.MaxStaleLimit = timeSpan;
+            Assert.Equal(timeSpan, cacheControl.MaxStaleLimit);
+            cacheControl.MinFresh = timeSpan;
+            Assert.Equal(timeSpan, cacheControl.MinFresh);
+
+            // String collection properties
+            Assert.NotNull(cacheControl.NoCacheHeaders);
+            Assert.Throws<ArgumentException>(() => cacheControl.NoCacheHeaders.Add(null));
+            Assert.Throws<FormatException>(() => cacheControl.NoCacheHeaders.Add("invalid token"));
+            cacheControl.NoCacheHeaders.Add("token");
+            Assert.Equal(1, cacheControl.NoCacheHeaders.Count);
+            Assert.Equal("token", cacheControl.NoCacheHeaders.First());
+
+            Assert.NotNull(cacheControl.PrivateHeaders);
+            Assert.Throws<ArgumentException>(() => cacheControl.PrivateHeaders.Add(null));
+            Assert.Throws<FormatException>(() => cacheControl.PrivateHeaders.Add("invalid token"));
+            cacheControl.PrivateHeaders.Add("token");
+            Assert.Equal(1, cacheControl.PrivateHeaders.Count);
+            Assert.Equal("token", cacheControl.PrivateHeaders.First());
+
+            // NameValueHeaderValue collection property
+            Assert.NotNull(cacheControl.Extensions);
+            Assert.Throws<ArgumentNullException>(() => cacheControl.Extensions.Add(null));
+            cacheControl.Extensions.Add(new NameValueHeaderValue("name", "value"));
+            Assert.Equal(1, cacheControl.Extensions.Count);
+            Assert.Equal(new NameValueHeaderValue("name", "value"), cacheControl.Extensions.First());
+        }
+
+        [Fact]
+        public void ToString_UseRequestDirectiveValues_AllSerializedCorrectly()
+        {
+            var cacheControl = new CacheControlHeaderValue();
+            Assert.Equal("", cacheControl.ToString());
+
+            // Note that we allow all combinations of all properties even though the RFC specifies rules what value
+            // can be used together.
+            // Also for property pairs (bool property + collection property) like 'NoCache' and 'NoCacheHeaders' the
+            // caller needs to set the bool property in order for the collection to be populated as string.
+
+            // Cache Request Directive sample
+            cacheControl.NoStore = true;
+            Assert.Equal("no-store", cacheControl.ToString());
+            cacheControl.NoCache = true;
+            Assert.Equal("no-store, no-cache", cacheControl.ToString());
+            cacheControl.MaxAge = new TimeSpan(0, 1, 10);
+            Assert.Equal("no-store, no-cache, max-age=70", cacheControl.ToString());
+            cacheControl.MaxStale = true;
+            Assert.Equal("no-store, no-cache, max-age=70, max-stale", cacheControl.ToString());
+            cacheControl.MaxStaleLimit = new TimeSpan(0, 2, 5);
+            Assert.Equal("no-store, no-cache, max-age=70, max-stale=125", cacheControl.ToString());
+            cacheControl.MinFresh = new TimeSpan(0, 3, 0);
+            Assert.Equal("no-store, no-cache, max-age=70, max-stale=125, min-fresh=180", cacheControl.ToString());
+
+            cacheControl = new CacheControlHeaderValue();
+            cacheControl.NoTransform = true;
+            Assert.Equal("no-transform", cacheControl.ToString());
+            cacheControl.OnlyIfCached = true;
+            Assert.Equal("no-transform, only-if-cached", cacheControl.ToString());
+            cacheControl.Extensions.Add(new NameValueHeaderValue("custom"));
+            cacheControl.Extensions.Add(new NameValueHeaderValue("customName", "customValue"));
+            Assert.Equal("no-transform, only-if-cached, custom, customName=customValue", cacheControl.ToString());
+
+            cacheControl = new CacheControlHeaderValue();
+            cacheControl.Extensions.Add(new NameValueHeaderValue("custom"));
+            Assert.Equal("custom", cacheControl.ToString());
+        }
+
+        [Fact]
+        public void ToString_UseResponseDirectiveValues_AllSerializedCorrectly()
+        {
+            var cacheControl = new CacheControlHeaderValue();
+            Assert.Equal("", cacheControl.ToString());
+
+            cacheControl.NoCache = true;
+            Assert.Equal("no-cache", cacheControl.ToString());
+            cacheControl.NoCacheHeaders.Add("token1");
+            Assert.Equal("no-cache=\"token1\"", cacheControl.ToString());
+            cacheControl.Public = true;
+            Assert.Equal("public, no-cache=\"token1\"", cacheControl.ToString());
+
+            cacheControl = new CacheControlHeaderValue();
+            cacheControl.Private = true;
+            Assert.Equal("private", cacheControl.ToString());
+            cacheControl.PrivateHeaders.Add("token2");
+            cacheControl.PrivateHeaders.Add("token3");
+            Assert.Equal("private=\"token2, token3\"", cacheControl.ToString());
+            cacheControl.MustRevalidate = true;
+            Assert.Equal("must-revalidate, private=\"token2, token3\"", cacheControl.ToString());
+            cacheControl.ProxyRevalidate = true;
+            Assert.Equal("must-revalidate, proxy-revalidate, private=\"token2, token3\"", cacheControl.ToString());
+        }
+
+        [Fact]
+        public void GetHashCode_CompareValuesWithBoolFieldsSet_MatchExpectation()
+        {
+            // Verify that different bool fields return different hash values.
+            var values = new CacheControlHeaderValue[9];
+
+            for (int i = 0; i < values.Length; i++)
+            {
+                values[i] = new CacheControlHeaderValue();
+            }
+
+            values[0].ProxyRevalidate = true;
+            values[1].NoCache = true;
+            values[2].NoStore = true;
+            values[3].MaxStale = true;
+            values[4].NoTransform = true;
+            values[5].OnlyIfCached = true;
+            values[6].Public = true;
+            values[7].Private = true;
+            values[8].MustRevalidate = true;
+
+            // Only one bool field set. All hash codes should differ
+            for (int i = 0; i < values.Length; i++)
+            {
+                for (int j = 0; j < values.Length; j++)
+                {
+                    if (i != j)
+                    {
+                        CompareHashCodes(values[i], values[j], false);
+                    }
+                }
+            }
+
+            // Validate that two instances with the same bool fields set are equal.
+            values[0].NoCache = true;
+            CompareHashCodes(values[0], values[1], false);
+            values[1].ProxyRevalidate = true;
+            CompareHashCodes(values[0], values[1], true);
+        }
+
+        [Fact]
+        public void GetHashCode_CompareValuesWithTimeSpanFieldsSet_MatchExpectation()
+        {
+            // Verify that different timespan fields return different hash values.
+            var values = new CacheControlHeaderValue[4];
+
+            for (int i = 0; i < values.Length; i++)
+            {
+                values[i] = new CacheControlHeaderValue();
+            }
+
+            values[0].MaxAge = new TimeSpan(0, 1, 1);
+            values[1].MaxStaleLimit = new TimeSpan(0, 1, 1);
+            values[2].MinFresh = new TimeSpan(0, 1, 1);
+            values[3].SharedMaxAge = new TimeSpan(0, 1, 1);
+
+            // Only one timespan field set. All hash codes should differ
+            for (int i = 0; i < values.Length; i++)
+            {
+                for (int j = 0; j < values.Length; j++)
+                {
+                    if (i != j)
+                    {
+                        CompareHashCodes(values[i], values[j], false);
+                    }
+                }
+            }
+
+            values[0].MaxStaleLimit = new TimeSpan(0, 1, 2);
+            CompareHashCodes(values[0], values[1], false);
+
+            values[1].MaxAge = new TimeSpan(0, 1, 1);
+            values[1].MaxStaleLimit = new TimeSpan(0, 1, 2);
+            CompareHashCodes(values[0], values[1], true);
+        }
+
+        [Fact]
+        public void GetHashCode_CompareCollectionFieldsSet_MatchExpectation()
+        {
+            var cacheControl1 = new CacheControlHeaderValue();
+            var cacheControl2 = new CacheControlHeaderValue();
+            var cacheControl3 = new CacheControlHeaderValue();
+            var cacheControl4 = new CacheControlHeaderValue();
+            var cacheControl5 = new CacheControlHeaderValue();
+
+            cacheControl1.NoCache = true;
+            cacheControl1.NoCacheHeaders.Add("token2");
+
+            cacheControl2.NoCache = true;
+            cacheControl2.NoCacheHeaders.Add("token1");
+            cacheControl2.NoCacheHeaders.Add("token2");
+
+            CompareHashCodes(cacheControl1, cacheControl2, false);
+
+            cacheControl1.NoCacheHeaders.Add("token1");
+            CompareHashCodes(cacheControl1, cacheControl2, true);
+
+            // Since NoCache and Private generate different hash codes, even if NoCacheHeaders and PrivateHeaders 
+            // have the same values, the hash code will be different.
+            cacheControl3.Private = true;
+            cacheControl3.PrivateHeaders.Add("token2");
+            CompareHashCodes(cacheControl1, cacheControl3, false);
+
+
+            cacheControl4.Extensions.Add(new NameValueHeaderValue("custom"));
+            CompareHashCodes(cacheControl1, cacheControl4, false);
+
+            cacheControl5.Extensions.Add(new NameValueHeaderValue("customN", "customV"));
+            cacheControl5.Extensions.Add(new NameValueHeaderValue("custom"));
+            CompareHashCodes(cacheControl4, cacheControl5, false);
+
+            cacheControl4.Extensions.Add(new NameValueHeaderValue("customN", "customV"));
+            CompareHashCodes(cacheControl4, cacheControl5, true);
+        }
+
+        [Fact]
+        public void Equals_CompareValuesWithBoolFieldsSet_MatchExpectation()
+        {
+            // Verify that different bool fields return different hash values.
+            var values = new CacheControlHeaderValue[9];
+
+            for (int i = 0; i < values.Length; i++)
+            {
+                values[i] = new CacheControlHeaderValue();
+            }
+
+            values[0].ProxyRevalidate = true;
+            values[1].NoCache = true;
+            values[2].NoStore = true;
+            values[3].MaxStale = true;
+            values[4].NoTransform = true;
+            values[5].OnlyIfCached = true;
+            values[6].Public = true;
+            values[7].Private = true;
+            values[8].MustRevalidate = true;
+
+            // Only one bool field set. All hash codes should differ
+            for (int i = 0; i < values.Length; i++)
+            {
+                for (int j = 0; j < values.Length; j++)
+                {
+                    if (i != j)
+                    {
+                        CompareValues(values[i], values[j], false);
+                    }
+                }
+            }
+
+            // Validate that two instances with the same bool fields set are equal.
+            values[0].NoCache = true;
+            CompareValues(values[0], values[1], false);
+            values[1].ProxyRevalidate = true;
+            CompareValues(values[0], values[1], true);
+        }
+
+        [Fact]
+        public void Equals_CompareValuesWithTimeSpanFieldsSet_MatchExpectation()
+        {
+            // Verify that different timespan fields return different hash values.
+            var values = new CacheControlHeaderValue[4];
+
+            for (int i = 0; i < values.Length; i++)
+            {
+                values[i] = new CacheControlHeaderValue();
+            }
+
+            values[0].MaxAge = new TimeSpan(0, 1, 1);
+            values[1].MaxStaleLimit = new TimeSpan(0, 1, 1);
+            values[2].MinFresh = new TimeSpan(0, 1, 1);
+            values[3].SharedMaxAge = new TimeSpan(0, 1, 1);
+
+            // Only one timespan field set. All hash codes should differ
+            for (int i = 0; i < values.Length; i++)
+            {
+                for (int j = 0; j < values.Length; j++)
+                {
+                    if (i != j)
+                    {
+                        CompareValues(values[i], values[j], false);
+                    }
+                }
+            }
+
+            values[0].MaxStaleLimit = new TimeSpan(0, 1, 2);
+            CompareValues(values[0], values[1], false);
+
+            values[1].MaxAge = new TimeSpan(0, 1, 1);
+            values[1].MaxStaleLimit = new TimeSpan(0, 1, 2);
+            CompareValues(values[0], values[1], true);
+
+            var value1 = new CacheControlHeaderValue();
+            value1.MaxStale = true;
+            var value2 = new CacheControlHeaderValue();
+            value2.MaxStale = true;
+            CompareValues(value1, value2, true);
+
+            value2.MaxStaleLimit = new TimeSpan(1, 2, 3);
+            CompareValues(value1, value2, false);
+        }
+
+        [Fact]
+        public void Equals_CompareCollectionFieldsSet_MatchExpectation()
+        {
+            var cacheControl1 = new CacheControlHeaderValue();
+            var cacheControl2 = new CacheControlHeaderValue();
+            var cacheControl3 = new CacheControlHeaderValue();
+            var cacheControl4 = new CacheControlHeaderValue();
+            var cacheControl5 = new CacheControlHeaderValue();
+            var cacheControl6 = new CacheControlHeaderValue();
+
+            cacheControl1.NoCache = true;
+            cacheControl1.NoCacheHeaders.Add("token2");
+
+            Assert.False(cacheControl1.Equals(null), "Compare with 'null'");
+
+            cacheControl2.NoCache = true;
+            cacheControl2.NoCacheHeaders.Add("token1");
+            cacheControl2.NoCacheHeaders.Add("token2");
+
+            CompareValues(cacheControl1, cacheControl2, false);
+
+            cacheControl1.NoCacheHeaders.Add("token1");
+            CompareValues(cacheControl1, cacheControl2, true);
+
+            // Since NoCache and Private generate different hash codes, even if NoCacheHeaders and PrivateHeaders
+            // have the same values, the hash code will be different.
+            cacheControl3.Private = true;
+            cacheControl3.PrivateHeaders.Add("token2");
+            CompareValues(cacheControl1, cacheControl3, false);
+
+            cacheControl4.Private = true;
+            cacheControl4.PrivateHeaders.Add("token3");
+            CompareValues(cacheControl3, cacheControl4, false);
+
+            cacheControl5.Extensions.Add(new NameValueHeaderValue("custom"));
+            CompareValues(cacheControl1, cacheControl5, false);
+
+            cacheControl6.Extensions.Add(new NameValueHeaderValue("customN", "customV"));
+            cacheControl6.Extensions.Add(new NameValueHeaderValue("custom"));
+            CompareValues(cacheControl5, cacheControl6, false);
+
+            cacheControl5.Extensions.Add(new NameValueHeaderValue("customN", "customV"));
+            CompareValues(cacheControl5, cacheControl6, true);
+        }
+
+        [Fact]
+        public void TryParse_DifferentValidScenarios_AllReturnTrue()
+        {
+            var expected = new CacheControlHeaderValue();
+            expected.NoCache = true;
+            CheckValidTryParse(" , no-cache ,,", expected);
+
+            expected = new CacheControlHeaderValue();
+            expected.NoCache = true;
+            expected.NoCacheHeaders.Add("token1");
+            expected.NoCacheHeaders.Add("token2");
+            CheckValidTryParse("no-cache=\"token1, token2\"", expected);
+
+            expected = new CacheControlHeaderValue();
+            expected.NoStore = true;
+            expected.MaxAge = new TimeSpan(0, 0, 125);
+            expected.MaxStale = true;
+            CheckValidTryParse(" no-store , max-age = 125, max-stale,", expected);
+
+            expected = new CacheControlHeaderValue();
+            expected.MinFresh = new TimeSpan(0, 0, 123);
+            expected.NoTransform = true;
+            expected.OnlyIfCached = true;
+            expected.Extensions.Add(new NameValueHeaderValue("custom"));
+            CheckValidTryParse("min-fresh=123, no-transform, only-if-cached, custom", expected);
+
+            expected = new CacheControlHeaderValue();
+            expected.Public = true;
+            expected.Private = true;
+            expected.PrivateHeaders.Add("token1");
+            expected.MustRevalidate = true;
+            expected.ProxyRevalidate = true;
+            expected.Extensions.Add(new NameValueHeaderValue("c", "d"));
+            expected.Extensions.Add(new NameValueHeaderValue("a", "b"));
+            CheckValidTryParse(",public, , private=\"token1\", must-revalidate, c=d, proxy-revalidate, a=b", expected);
+
+            expected = new CacheControlHeaderValue();
+            expected.Private = true;
+            expected.SharedMaxAge = new TimeSpan(0, 0, 1234567890);
+            expected.MaxAge = new TimeSpan(0, 0, 987654321);
+            CheckValidTryParse("s-maxage=1234567890, private, max-age = 987654321,", expected);
+
+            expected = new CacheControlHeaderValue();
+            expected.Extensions.Add(new NameValueHeaderValue("custom", ""));
+            CheckValidTryParse("custom=", expected);
+        }
+
+        [Theory]
+        [InlineData(null)]
+        [InlineData("")]
+        [InlineData("    ")]
+        // Token-only values
+        [InlineData("no-store=15")]
+        [InlineData("no-store=")]
+        [InlineData("no-transform=a")]
+        [InlineData("no-transform=")]
+        [InlineData("only-if-cached=\"x\"")]
+        [InlineData("only-if-cached=")]
+        [InlineData("public=\"x\"")]
+        [InlineData("public=")]
+        [InlineData("must-revalidate=\"1\"")]
+        [InlineData("must-revalidate=")]
+        [InlineData("proxy-revalidate=x")]
+        [InlineData("proxy-revalidate=")]
+        // Token with optional field-name list
+        [InlineData("no-cache=")]
+        [InlineData("no-cache=token")]
+        [InlineData("no-cache=\"token")]
+        [InlineData("no-cache=\"\"")] // at least one token expected as value
+        [InlineData("private=")]
+        [InlineData("private=token")]
+        [InlineData("private=\"token")]
+        [InlineData("private=\",\"")] // at least one token expected as value
+        [InlineData("private=\"=\"")]
+        // Token with delta-seconds value
+        [InlineData("max-age")]
+        [InlineData("max-age=")]
+        [InlineData("max-age=a")]
+        [InlineData("max-age=\"1\"")]
+        [InlineData("max-age=1.5")]
+        [InlineData("max-stale=")]
+        [InlineData("max-stale=a")]
+        [InlineData("max-stale=\"1\"")]
+        [InlineData("max-stale=1.5")]
+        [InlineData("min-fresh")]
+        [InlineData("min-fresh=")]
+        [InlineData("min-fresh=a")]
+        [InlineData("min-fresh=\"1\"")]
+        [InlineData("min-fresh=1.5")]
+        [InlineData("s-maxage")]
+        [InlineData("s-maxage=")]
+        [InlineData("s-maxage=a")]
+        [InlineData("s-maxage=\"1\"")]
+        [InlineData("s-maxage=1.5")]
+        // Invalid Extension values
+        [InlineData("custom value")]
+        public void TryParse_DifferentInvalidScenarios_ReturnsFalse(string input)
+        {
+            CheckInvalidTryParse(input);
+        }
+
+        [Fact]
+        public void Parse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            // Just verify parser is implemented correctly. Don't try to test syntax parsed by CacheControlHeaderValue.
+            var expected = new CacheControlHeaderValue();
+            expected.NoStore = true;
+            expected.MinFresh = new TimeSpan(0, 2, 3);
+            CheckValidParse(" , no-store, min-fresh=123", expected);
+
+            expected = new CacheControlHeaderValue();
+            expected.MaxStale = true;
+            expected.NoCache = true;
+            expected.NoCacheHeaders.Add("t");
+            CheckValidParse("max-stale, no-cache=\"t\", ,,", expected);
+
+            expected = new CacheControlHeaderValue();
+            expected.Extensions.Add(new NameValueHeaderValue("custom"));
+            CheckValidParse("custom =", expected);
+
+            expected = new CacheControlHeaderValue();
+            expected.Extensions.Add(new NameValueHeaderValue("custom", ""));
+            CheckValidParse("custom =", expected);
+        }
+
+        [Fact]
+        public void Parse_SetOfInvalidValueStrings_Throws()
+        {
+            CheckInvalidParse(null);
+            CheckInvalidParse("");
+            CheckInvalidParse("   ");
+            CheckInvalidParse("no-cache,=");
+            CheckInvalidParse("max-age=123x");
+            CheckInvalidParse("=no-cache");
+            CheckInvalidParse("no-cache no-store");
+            CheckInvalidParse("会");
+        }
+
+        [Fact]
+        public void TryParse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            // Just verify parser is implemented correctly. Don't try to test syntax parsed by CacheControlHeaderValue.
+            var expected = new CacheControlHeaderValue();
+            expected.NoStore = true;
+            expected.MinFresh = new TimeSpan(0, 2, 3);
+            CheckValidTryParse(" , no-store, min-fresh=123", expected);
+
+            expected = new CacheControlHeaderValue();
+            expected.MaxStale = true;
+            expected.NoCache = true;
+            expected.NoCacheHeaders.Add("t");
+            CheckValidTryParse("max-stale, no-cache=\"t\", ,,", expected);
+
+            expected = new CacheControlHeaderValue();
+            expected.Extensions.Add(new NameValueHeaderValue("custom"));
+            CheckValidTryParse("custom = ", expected);
+
+            expected = new CacheControlHeaderValue();
+            expected.Extensions.Add(new NameValueHeaderValue("custom", ""));
+            CheckValidTryParse("custom =", expected);
+        }
+
+        [Fact]
+        public void TryParse_SetOfInvalidValueStrings_ReturnsFalse()
+        {
+            CheckInvalidTryParse("no-cache,=");
+            CheckInvalidTryParse("max-age=123x");
+            CheckInvalidTryParse("=no-cache");
+            CheckInvalidTryParse("no-cache no-store");
+            CheckInvalidTryParse("会");
+        }
+
+        #region Helper methods
+
+        private void CompareHashCodes(CacheControlHeaderValue x, CacheControlHeaderValue y, bool areEqual)
+        {
+            if (areEqual)
+            {
+                Assert.Equal(x.GetHashCode(), y.GetHashCode());
+            }
+            else
+            {
+                Assert.NotEqual(x.GetHashCode(), y.GetHashCode());
+            }
+        }
+
+        private void CompareValues(CacheControlHeaderValue x, CacheControlHeaderValue y, bool areEqual)
+        {
+            Assert.Equal(areEqual, x.Equals(y));
+            Assert.Equal(areEqual, y.Equals(x));
+        }
+
+        private void CheckValidParse(string input, CacheControlHeaderValue expectedResult)
+        {
+            var result = CacheControlHeaderValue.Parse(input);
+            Assert.Equal(expectedResult, result);
+        }
+
+        private void CheckInvalidParse(string input)
+        {
+            Assert.Throws<FormatException>(() => CacheControlHeaderValue.Parse(input));
+        }
+
+        private void CheckValidTryParse(string input, CacheControlHeaderValue expectedResult)
+        {
+            CacheControlHeaderValue result = null;
+            Assert.True(CacheControlHeaderValue.TryParse(input, out result));
+            Assert.Equal(expectedResult, result);
+        }
+
+        private void CheckInvalidTryParse(string input)
+        {
+            CacheControlHeaderValue result = null;
+            Assert.False(CacheControlHeaderValue.TryParse(input, out result));
+            Assert.Null(result);
+        }
+
+        #endregion
+    }
+}
diff --git a/src/Http/Headers/test/ContentDispositionHeaderValueTest.cs b/src/Http/Headers/test/ContentDispositionHeaderValueTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ad1f7fce1f4ef6b0df83a97c0ba47f1ab618d877
--- /dev/null
+++ b/src/Http/Headers/test/ContentDispositionHeaderValueTest.cs
@@ -0,0 +1,622 @@
+// 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 Xunit;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class ContentDispositionHeaderValueTest
+    {
+        [Fact]
+        public void Ctor_ContentDispositionNull_Throw()
+        {
+            Assert.Throws<ArgumentException>(() => new ContentDispositionHeaderValue(null));
+        }
+
+        [Fact]
+        public void Ctor_ContentDispositionEmpty_Throw()
+        {
+            // null and empty should be treated the same. So we also throw for empty strings.
+            Assert.Throws<ArgumentException>(() => new ContentDispositionHeaderValue(string.Empty));
+        }
+
+        [Fact]
+        public void Ctor_ContentDispositionInvalidFormat_ThrowFormatException()
+        {
+            // When adding values using strongly typed objects, no leading/trailing LWS (whitespaces) are allowed.
+            AssertFormatException(" inline ");
+            AssertFormatException(" inline");
+            AssertFormatException("inline ");
+            AssertFormatException("\"inline\"");
+            AssertFormatException("te xt");
+            AssertFormatException("te=xt");
+            AssertFormatException("teäxt");
+            AssertFormatException("text;");
+            AssertFormatException("te/xt;");
+            AssertFormatException("inline; name=someName; ");
+            AssertFormatException("text;name=someName"); // ctor takes only disposition-type name, no parameters
+        }
+
+        [Fact]
+        public void Ctor_ContentDispositionValidFormat_SuccessfullyCreated()
+        {
+            var contentDisposition = new ContentDispositionHeaderValue("inline");
+            Assert.Equal("inline", contentDisposition.DispositionType);
+            Assert.Equal(0, contentDisposition.Parameters.Count);
+            Assert.Null(contentDisposition.Name.Value);
+            Assert.Null(contentDisposition.FileName.Value);
+            Assert.Null(contentDisposition.CreationDate);
+            Assert.Null(contentDisposition.ModificationDate);
+            Assert.Null(contentDisposition.ReadDate);
+            Assert.Null(contentDisposition.Size);
+        }
+
+        [Fact]
+        public void Parameters_AddNull_Throw()
+        {
+            var contentDisposition = new ContentDispositionHeaderValue("inline");
+            Assert.Throws<ArgumentNullException>(() => contentDisposition.Parameters.Add(null));
+        }
+
+        [Fact]
+        public void ContentDisposition_SetAndGetContentDisposition_MatchExpectations()
+        {
+            var contentDisposition = new ContentDispositionHeaderValue("inline");
+            Assert.Equal("inline", contentDisposition.DispositionType);
+
+            contentDisposition.DispositionType = "attachment";
+            Assert.Equal("attachment", contentDisposition.DispositionType);
+        }
+
+        [Fact]
+        public void Name_SetNameAndValidateObject_ParametersEntryForNameAdded()
+        {
+            var contentDisposition = new ContentDispositionHeaderValue("inline");
+            contentDisposition.Name = "myname";
+            Assert.Equal("myname", contentDisposition.Name);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("name", contentDisposition.Parameters.First().Name);
+
+            contentDisposition.Name = null;
+            Assert.Null(contentDisposition.Name.Value);
+            Assert.Equal(0, contentDisposition.Parameters.Count);
+            contentDisposition.Name = null; // It's OK to set it again to null; no exception.
+        }
+
+        [Fact]
+        public void Name_AddNameParameterThenUseProperty_ParametersEntryIsOverwritten()
+        {
+            var contentDisposition = new ContentDispositionHeaderValue("inline");
+
+            // Note that uppercase letters are used. Comparison should happen case-insensitive.
+            NameValueHeaderValue name = new NameValueHeaderValue("NAME", "old_name");
+            contentDisposition.Parameters.Add(name);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("NAME", contentDisposition.Parameters.First().Name);
+
+            contentDisposition.Name = "new_name";
+            Assert.Equal("new_name", contentDisposition.Name);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("NAME", contentDisposition.Parameters.First().Name);
+
+            contentDisposition.Parameters.Remove(name);
+            Assert.Null(contentDisposition.Name.Value);
+        }
+
+        [Fact]
+        public void FileName_AddNameParameterThenUseProperty_ParametersEntryIsOverwritten()
+        {
+            var contentDisposition = new ContentDispositionHeaderValue("inline");
+
+            // Note that uppercase letters are used. Comparison should happen case-insensitive.
+            var fileName = new NameValueHeaderValue("FILENAME", "old_name");
+            contentDisposition.Parameters.Add(fileName);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("FILENAME", contentDisposition.Parameters.First().Name);
+
+            contentDisposition.FileName = "new_name";
+            Assert.Equal("new_name", contentDisposition.FileName);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("FILENAME", contentDisposition.Parameters.First().Name);
+
+            contentDisposition.Parameters.Remove(fileName);
+            Assert.Null(contentDisposition.FileName.Value);
+        }
+
+        [Fact]
+        public void FileName_NeedsEncoding_EncodedAndDecodedCorrectly()
+        {
+            var contentDisposition = new ContentDispositionHeaderValue("inline");
+
+            contentDisposition.FileName = "FileÃName.bat";
+            Assert.Equal("FileÃName.bat", contentDisposition.FileName);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("filename", contentDisposition.Parameters.First().Name);
+            Assert.Equal("\"=?utf-8?B?RmlsZcODTmFtZS5iYXQ=?=\"", contentDisposition.Parameters.First().Value);
+
+            contentDisposition.Parameters.Remove(contentDisposition.Parameters.First());
+            Assert.Null(contentDisposition.FileName.Value);
+        }
+
+        [Fact]
+        public void FileName_UnknownOrBadEncoding_PropertyFails()
+        {
+            var contentDisposition = new ContentDispositionHeaderValue("inline");
+
+            // Note that uppercase letters are used. Comparison should happen case-insensitive.
+            var fileName = new NameValueHeaderValue("FILENAME", "\"=?utf-99?Q?R=mlsZcODTmFtZS5iYXQ=?=\"");
+            contentDisposition.Parameters.Add(fileName);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("FILENAME", contentDisposition.Parameters.First().Name);
+            Assert.Equal("\"=?utf-99?Q?R=mlsZcODTmFtZS5iYXQ=?=\"", contentDisposition.Parameters.First().Value);
+            Assert.Equal("=?utf-99?Q?R=mlsZcODTmFtZS5iYXQ=?=", contentDisposition.FileName);
+
+            contentDisposition.FileName = "new_name";
+            Assert.Equal("new_name", contentDisposition.FileName);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("FILENAME", contentDisposition.Parameters.First().Name);
+
+            contentDisposition.Parameters.Remove(fileName);
+            Assert.Null(contentDisposition.FileName.Value);
+        }
+
+        [Fact]
+        public void FileNameStar_AddNameParameterThenUseProperty_ParametersEntryIsOverwritten()
+        {
+            var contentDisposition = new ContentDispositionHeaderValue("inline");
+
+            // Note that uppercase letters are used. Comparison should happen case-insensitive.
+            var fileNameStar = new NameValueHeaderValue("FILENAME*", "old_name");
+            contentDisposition.Parameters.Add(fileNameStar);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("FILENAME*", contentDisposition.Parameters.First().Name);
+            Assert.Null(contentDisposition.FileNameStar.Value); // Decode failure
+
+            contentDisposition.FileNameStar = "new_name";
+            Assert.Equal("new_name", contentDisposition.FileNameStar);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("FILENAME*", contentDisposition.Parameters.First().Name);
+            Assert.Equal("UTF-8\'\'new_name", contentDisposition.Parameters.First().Value);
+
+            contentDisposition.Parameters.Remove(fileNameStar);
+            Assert.Null(contentDisposition.FileNameStar.Value);
+        }
+
+        [Fact]
+        public void FileNameStar_NeedsEncoding_EncodedAndDecodedCorrectly()
+        {
+            var contentDisposition = new ContentDispositionHeaderValue("inline");
+
+            contentDisposition.FileNameStar = "FileÃName.bat";
+            Assert.Equal("FileÃName.bat", contentDisposition.FileNameStar);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("filename*", contentDisposition.Parameters.First().Name);
+            Assert.Equal("UTF-8\'\'File%C3%83Name.bat", contentDisposition.Parameters.First().Value);
+
+            contentDisposition.Parameters.Remove(contentDisposition.Parameters.First());
+            Assert.Null(contentDisposition.FileNameStar.Value);
+        }
+
+        [Fact]
+        public void FileNameStar_UnknownOrBadEncoding_PropertyFails()
+        {
+            var contentDisposition = new ContentDispositionHeaderValue("inline");
+
+            // Note that uppercase letters are used. Comparison should happen case-insensitive.
+            var fileNameStar = new NameValueHeaderValue("FILENAME*", "utf-99'lang'File%CZName.bat");
+            contentDisposition.Parameters.Add(fileNameStar);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("FILENAME*", contentDisposition.Parameters.First().Name);
+            Assert.Equal("utf-99'lang'File%CZName.bat", contentDisposition.Parameters.First().Value);
+            Assert.Null(contentDisposition.FileNameStar.Value); // Decode failure
+
+            contentDisposition.FileNameStar = "new_name";
+            Assert.Equal("new_name", contentDisposition.FileNameStar);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("FILENAME*", contentDisposition.Parameters.First().Name);
+
+            contentDisposition.Parameters.Remove(fileNameStar);
+            Assert.Null(contentDisposition.FileNameStar.Value);
+        }
+
+        [Fact]
+        public void Dates_AddDateParameterThenUseProperty_ParametersEntryIsOverwritten()
+        {
+            string validDateString = "\"Tue, 15 Nov 1994 08:12:31 GMT\"";
+            DateTimeOffset validDate = DateTimeOffset.Parse("Tue, 15 Nov 1994 08:12:31 GMT");
+
+            var contentDisposition = new ContentDispositionHeaderValue("inline");
+
+            // Note that uppercase letters are used. Comparison should happen case-insensitive.
+            var dateParameter = new NameValueHeaderValue("Creation-DATE", validDateString);
+            contentDisposition.Parameters.Add(dateParameter);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("Creation-DATE", contentDisposition.Parameters.First().Name);
+
+            Assert.Equal(validDate, contentDisposition.CreationDate);
+
+            var newDate = validDate.AddSeconds(1);
+            contentDisposition.CreationDate = newDate;
+            Assert.Equal(newDate, contentDisposition.CreationDate);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("Creation-DATE", contentDisposition.Parameters.First().Name);
+            Assert.Equal("\"Tue, 15 Nov 1994 08:12:32 GMT\"", contentDisposition.Parameters.First().Value);
+
+            contentDisposition.Parameters.Remove(dateParameter);
+            Assert.Null(contentDisposition.CreationDate);
+        }
+
+        [Fact]
+        public void Dates_InvalidDates_PropertyFails()
+        {
+            string invalidDateString = "\"Tue, 15 Nov 94 08:12 GMT\"";
+
+            var contentDisposition = new ContentDispositionHeaderValue("inline");
+
+            // Note that uppercase letters are used. Comparison should happen case-insensitive.
+            var dateParameter = new NameValueHeaderValue("read-DATE", invalidDateString);
+            contentDisposition.Parameters.Add(dateParameter);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("read-DATE", contentDisposition.Parameters.First().Name);
+
+            Assert.Null(contentDisposition.ReadDate);
+
+            contentDisposition.ReadDate = null;
+            Assert.Null(contentDisposition.ReadDate);
+            Assert.Equal(0, contentDisposition.Parameters.Count);
+        }
+
+        [Fact]
+        public void Size_AddSizeParameterThenUseProperty_ParametersEntryIsOverwritten()
+        {
+            var contentDisposition = new ContentDispositionHeaderValue("inline");
+
+            // Note that uppercase letters are used. Comparison should happen case-insensitive.
+            var sizeParameter = new NameValueHeaderValue("SIZE", "279172874239");
+            contentDisposition.Parameters.Add(sizeParameter);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("SIZE", contentDisposition.Parameters.First().Name);
+            Assert.Equal(279172874239, contentDisposition.Size);
+
+            contentDisposition.Size = 279172874240;
+            Assert.Equal(279172874240, contentDisposition.Size);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("SIZE", contentDisposition.Parameters.First().Name);
+
+            contentDisposition.Parameters.Remove(sizeParameter);
+            Assert.Null(contentDisposition.Size);
+        }
+
+        [Fact]
+        public void Size_InvalidSizes_PropertyFails()
+        {
+            var contentDisposition = new ContentDispositionHeaderValue("inline");
+
+            // Note that uppercase letters are used. Comparison should happen case-insensitive.
+            var sizeParameter = new NameValueHeaderValue("SIZE", "-279172874239");
+            contentDisposition.Parameters.Add(sizeParameter);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("SIZE", contentDisposition.Parameters.First().Name);
+            Assert.Null(contentDisposition.Size);
+
+            // Negatives not allowed
+            Assert.Throws<ArgumentOutOfRangeException>(() => contentDisposition.Size = -279172874240);
+            Assert.Null(contentDisposition.Size);
+            Assert.Equal(1, contentDisposition.Parameters.Count);
+            Assert.Equal("SIZE", contentDisposition.Parameters.First().Name);
+
+            contentDisposition.Parameters.Remove(sizeParameter);
+            Assert.Null(contentDisposition.Size);
+        }
+
+        [Fact]
+        public void ToString_UseDifferentContentDispositions_AllSerializedCorrectly()
+        {
+            var contentDisposition = new ContentDispositionHeaderValue("inline");
+            Assert.Equal("inline", contentDisposition.ToString());
+
+            contentDisposition.Name = "myname";
+            Assert.Equal("inline; name=myname", contentDisposition.ToString());
+
+            contentDisposition.FileName = "my File Name";
+            Assert.Equal("inline; name=myname; filename=\"my File Name\"", contentDisposition.ToString());
+
+            contentDisposition.CreationDate = new DateTimeOffset(new DateTime(2011, 2, 15), new TimeSpan(-8, 0, 0));
+            Assert.Equal("inline; name=myname; filename=\"my File Name\"; creation-date="
+                + "\"Tue, 15 Feb 2011 08:00:00 GMT\"", contentDisposition.ToString());
+
+            contentDisposition.Parameters.Add(new NameValueHeaderValue("custom", "\"custom value\""));
+            Assert.Equal("inline; name=myname; filename=\"my File Name\"; creation-date="
+                + "\"Tue, 15 Feb 2011 08:00:00 GMT\"; custom=\"custom value\"",  contentDisposition.ToString());
+
+            contentDisposition.Name = null;
+            Assert.Equal("inline; filename=\"my File Name\"; creation-date="
+                + "\"Tue, 15 Feb 2011 08:00:00 GMT\"; custom=\"custom value\"", contentDisposition.ToString());
+
+            contentDisposition.FileNameStar = "File%Name";
+            Assert.Equal("inline; filename=\"my File Name\"; creation-date="
+                + "\"Tue, 15 Feb 2011 08:00:00 GMT\"; custom=\"custom value\"; filename*=UTF-8\'\'File%25Name",
+                contentDisposition.ToString());
+
+            contentDisposition.FileName = null;
+            Assert.Equal("inline; creation-date=\"Tue, 15 Feb 2011 08:00:00 GMT\"; custom=\"custom value\";"
+                + " filename*=UTF-8\'\'File%25Name", contentDisposition.ToString());
+
+            contentDisposition.CreationDate = null;
+            Assert.Equal("inline; custom=\"custom value\"; filename*=UTF-8\'\'File%25Name",
+                contentDisposition.ToString());
+        }
+
+        [Fact]
+        public void GetHashCode_UseContentDispositionWithAndWithoutParameters_SameOrDifferentHashCodes()
+        {
+            var contentDisposition1 = new ContentDispositionHeaderValue("inline");
+            var contentDisposition2 = new ContentDispositionHeaderValue("inline");
+            contentDisposition2.Name = "myname";
+            var contentDisposition3 = new ContentDispositionHeaderValue("inline");
+            contentDisposition3.Parameters.Add(new NameValueHeaderValue("name", "value"));
+            var contentDisposition4 = new ContentDispositionHeaderValue("INLINE");
+            var contentDisposition5 = new ContentDispositionHeaderValue("INLINE");
+            contentDisposition5.Parameters.Add(new NameValueHeaderValue("NAME", "MYNAME"));
+
+            Assert.NotEqual(contentDisposition1.GetHashCode(), contentDisposition2.GetHashCode());
+            Assert.NotEqual(contentDisposition1.GetHashCode(), contentDisposition3.GetHashCode());
+            Assert.NotEqual(contentDisposition2.GetHashCode(), contentDisposition3.GetHashCode());
+            Assert.Equal(contentDisposition1.GetHashCode(), contentDisposition4.GetHashCode());
+            Assert.Equal(contentDisposition2.GetHashCode(), contentDisposition5.GetHashCode());
+        }
+
+        [Fact]
+        public void Equals_UseContentDispositionWithAndWithoutParameters_EqualOrNotEqualNoExceptions()
+        {
+            var contentDisposition1 = new ContentDispositionHeaderValue("inline");
+            var contentDisposition2 = new ContentDispositionHeaderValue("inline");
+            contentDisposition2.Name = "myName";
+            var contentDisposition3 = new ContentDispositionHeaderValue("inline");
+            contentDisposition3.Parameters.Add(new NameValueHeaderValue("name", "value"));
+            var contentDisposition4 = new ContentDispositionHeaderValue("INLINE");
+            var contentDisposition5 = new ContentDispositionHeaderValue("INLINE");
+            contentDisposition5.Parameters.Add(new NameValueHeaderValue("NAME", "MYNAME"));
+            var contentDisposition6 = new ContentDispositionHeaderValue("INLINE");
+            contentDisposition6.Parameters.Add(new NameValueHeaderValue("NAME", "MYNAME"));
+            contentDisposition6.Parameters.Add(new NameValueHeaderValue("custom", "value"));
+            var contentDisposition7 = new ContentDispositionHeaderValue("attachment");
+
+            Assert.False(contentDisposition1.Equals(contentDisposition2), "No params vs. name.");
+            Assert.False(contentDisposition2.Equals(contentDisposition1), "name vs. no params.");
+            Assert.False(contentDisposition1.Equals(null), "No params vs. <null>.");
+            Assert.False(contentDisposition1.Equals(contentDisposition3), "No params vs. custom param.");
+            Assert.False(contentDisposition2.Equals(contentDisposition3), "name vs. custom param.");
+            Assert.True(contentDisposition1.Equals(contentDisposition4), "Different casing.");
+            Assert.True(contentDisposition2.Equals(contentDisposition5), "Different casing in name.");
+            Assert.False(contentDisposition5.Equals(contentDisposition6), "name vs. custom param.");
+            Assert.False(contentDisposition1.Equals(contentDisposition7), "inline vs. text/other.");
+        }
+
+        [Fact]
+        public void Parse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            var expected = new ContentDispositionHeaderValue("inline");
+            CheckValidParse("\r\n inline  ", expected);
+            CheckValidParse("inline", expected);
+
+            // We don't have to test all possible input strings, since most of the pieces are handled by other parsers.
+            // The purpose of this test is to verify that these other parsers are combined correctly to build a
+            // Content-Disposition parser.
+            expected.Name = "myName";
+            CheckValidParse("\r\n inline  ;  name =   myName ", expected);
+            CheckValidParse("  inline;name=myName", expected);
+
+            expected.Name = null;
+            expected.DispositionType = "attachment";
+            expected.FileName = "foo-ae.html";
+            expected.Parameters.Add(new NameValueHeaderValue("filename*", "UTF-8''foo-%c3%a4.html"));
+            CheckValidParse(@"attachment; filename*=UTF-8''foo-%c3%a4.html; filename=foo-ae.html", expected);
+        }
+
+        [Fact]
+        public void Parse_SetOfInvalidValueStrings_Throws()
+        {
+            CheckInvalidParse("");
+            CheckInvalidParse("  ");
+            CheckInvalidParse(null);
+            CheckInvalidParse("inline会");
+            CheckInvalidParse("inline ,");
+            CheckInvalidParse("inline,");
+            CheckInvalidParse("inline; name=myName ,");
+            CheckInvalidParse("inline; name=myName,");
+            CheckInvalidParse("inline; name=my会Name");
+            CheckInvalidParse("inline/");
+        }
+
+        [Fact]
+        public void TryParse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            var expected = new ContentDispositionHeaderValue("inline");
+            CheckValidTryParse("\r\n inline  ", expected);
+            CheckValidTryParse("inline", expected);
+
+            // We don't have to test all possible input strings, since most of the pieces are handled by other parsers.
+            // The purpose of this test is to verify that these other parsers are combined correctly to build a
+            // Content-Disposition parser.
+            expected.Name = "myName";
+            CheckValidTryParse("\r\n inline  ;  name =   myName ", expected);
+            CheckValidTryParse("  inline;name=myName", expected);
+        }
+
+        [Fact]
+        public void TryParse_SetOfInvalidValueStrings_ReturnsFalse()
+        {
+            CheckInvalidTryParse("");
+            CheckInvalidTryParse("  ");
+            CheckInvalidTryParse(null);
+            CheckInvalidTryParse("inline会");
+            CheckInvalidTryParse("inline ,");
+            CheckInvalidTryParse("inline,");
+            CheckInvalidTryParse("inline; name=myName ,");
+            CheckInvalidTryParse("inline; name=myName,");
+            CheckInvalidTryParse("text/");
+        }
+
+        public static TheoryData<string, ContentDispositionHeaderValue> ValidContentDispositionTestCases = new TheoryData<string, ContentDispositionHeaderValue>()
+        {
+            { "inline", new ContentDispositionHeaderValue("inline") }, // @"This should be equivalent to not including the header at all."
+            { "inline;", new ContentDispositionHeaderValue("inline") },
+            { "inline;name=", new ContentDispositionHeaderValue("inline") { Parameters = { new NameValueHeaderValue("name", "") } } }, // TODO: passing in a null value causes a strange assert on CoreCLR before the test even starts. Not reproducable in the body of a test.
+            { "inline;name=value", new ContentDispositionHeaderValue("inline") { Name  = "value" } },
+            { "inline;name=value;", new ContentDispositionHeaderValue("inline") { Name  = "value" } },
+            { "inline;name=value;", new ContentDispositionHeaderValue("inline") { Name  = "value" } },
+            { @"inline; filename=""foo.html""", new ContentDispositionHeaderValue("inline") { FileName = @"""foo.html""" } },
+            { @"inline; filename=""Not an attachment!""", new ContentDispositionHeaderValue("inline") { FileName = @"""Not an attachment!""" } }, // 'inline', specifying a filename of Not an attachment! - this checks for proper parsing for disposition types.
+            { @"inline; filename=""foo.pdf""", new ContentDispositionHeaderValue("inline") { FileName = @"""foo.pdf""" } },
+            { "attachment", new ContentDispositionHeaderValue("attachment") },
+            { "ATTACHMENT", new ContentDispositionHeaderValue("ATTACHMENT") },
+            { @"attachment; filename=""foo.html""", new ContentDispositionHeaderValue("attachment") { FileName = @"""foo.html""" } },
+            { @"attachment; filename=""\""quoting\"" tested.html""", new ContentDispositionHeaderValue("attachment") { FileName = "\"\"quoting\" tested.html\"" } }, // 'attachment', specifying a filename of \"quoting\" tested.html (using double quotes around "quoting" to test... quoting)
+            { @"attachment; filename=""Here's a semicolon;.html""", new ContentDispositionHeaderValue("attachment") { FileName = @"""Here's a semicolon;.html""" } }, // , 'attachment', specifying a filename of Here's a semicolon;.html - this checks for proper parsing for parameters.
+            { @"attachment; foo=""bar""; filename=""foo.html""", new ContentDispositionHeaderValue(@"attachment") { FileName = @"""foo.html""", Parameters = { new NameValueHeaderValue("foo", @"""bar""") } } }, // 'attachment', specifying a filename of foo.html and an extension parameter "foo" which should be ignored (see <a href="http://greenbytes.de/tech/webdav/rfc2183.html#rfc.section.2.8">Section 2.8 of RFC 2183</a>.).
+            { @"attachment; foo=""\""\\"";filename=""foo.html""", new ContentDispositionHeaderValue(@"attachment") { FileName = @"""foo.html""", Parameters = { new NameValueHeaderValue("foo", @"""\""\\""") } } }, // 'attachment', specifying a filename of foo.html and an extension parameter "foo" which should be ignored (see <a href="http://greenbytes.de/tech/webdav/rfc2183.html#rfc.section.2.8">Section 2.8 of RFC 2183</a>.). The extension parameter actually uses backslash-escapes. This tests whether the UA properly skips the parameter.
+            { @"attachment; FILENAME=""foo.html""", new ContentDispositionHeaderValue("attachment") { FileName = @"""foo.html""" } },
+            { @"attachment; filename=foo.html", new ContentDispositionHeaderValue("attachment") { FileName = "foo.html" } }, // 'attachment', specifying a filename of foo.html using a token instead of a quoted-string.
+            { @"attachment; filename='foo.bar'", new ContentDispositionHeaderValue("attachment") { FileName = "'foo.bar'" } }, // 'attachment', specifying a filename of 'foo.bar' using single quotes.
+            { @"attachment; filename=""foo-ä.html""", new ContentDispositionHeaderValue("attachment" ) { Parameters = { new NameValueHeaderValue("filename", @"""foo-ä.html""") } } }, // 'attachment', specifying a filename of foo-ä.html, using plain ISO-8859-1
+            { @"attachment; filename=""foo-&#xc3;&#xa4;.html""", new ContentDispositionHeaderValue("attachment") { FileName = @"""foo-&#xc3;&#xa4;.html""" } }, // 'attachment', specifying a filename of foo-&#xc3;&#xa4;.html, which happens to be foo-ä.html using UTF-8 encoding.
+            { @"attachment; filename=""foo-%41.html""", new ContentDispositionHeaderValue("attachment") {  Parameters = { new NameValueHeaderValue("filename", @"""foo-%41.html""") } } },
+            { @"attachment; filename=""50%.html""", new ContentDispositionHeaderValue("attachment") { Parameters = { new NameValueHeaderValue("filename", @"""50%.html""") } } },
+            { @"attachment; filename=""foo-%\41.html""", new ContentDispositionHeaderValue("attachment") { Parameters = { new NameValueHeaderValue("filename", @"""foo-%\41.html""") } } }, // 'attachment', specifying a filename of foo-%41.html, using an escape character (this tests whether adding an escape character inside a %xx sequence can be used to disable the non-conformant %xx-unescaping).
+            { @"attachment; name=""foo-%41.html""", new ContentDispositionHeaderValue("attachment") { Name = @"""foo-%41.html""" } }, // 'attachment', specifying a <i>name</i> parameter of foo-%41.html. (this test was added to observe the behavior of the (unspecified) treatment of ""name"" as synonym for ""filename""; see <a href=""http://www.imc.org/ietf-smtp/mail-archive/msg05023.html"">Ned Freed's summary</a> where this comes from in MIME messages)
+            { @"attachment; filename=""ä-%41.html""", new ContentDispositionHeaderValue("attachment") { Parameters = { new NameValueHeaderValue("filename", @"""ä-%41.html""") } } }, // 'attachment', specifying a filename parameter of ä-%41.html. (this test was added to observe the behavior when non-ASCII characters and percent-hexdig sequences are combined)
+            { @"attachment; filename=""foo-%c3%a4-%e2%82%ac.html""", new ContentDispositionHeaderValue("attachment") { FileName = @"""foo-%c3%a4-%e2%82%ac.html""" } }, // 'attachment', specifying a filename of foo-%c3%a4-%e2%82%ac.html, using raw percent encoded UTF-8 to represent foo-ä-&#x20ac;.html
+            { @"attachment; filename =""foo.html""", new ContentDispositionHeaderValue("attachment") { FileName = @"""foo.html""" } },
+            { @"attachment; xfilename=foo.html", new ContentDispositionHeaderValue("attachment" ) { Parameters = { new NameValueHeaderValue("xfilename", "foo.html") } } },
+            { @"attachment; filename=""/foo.html""", new ContentDispositionHeaderValue("attachment") { FileName = @"""/foo.html""" } },
+            { @"attachment; creation-date=""Wed, 12 Feb 1997 16:29:51 -0500""", new ContentDispositionHeaderValue("attachment") { Parameters = { new NameValueHeaderValue("creation-date", @"""Wed, 12 Feb 1997 16:29:51 -0500""") } } },
+            { @"attachment; modification-date=""Wed, 12 Feb 1997 16:29:51 -0500""", new ContentDispositionHeaderValue("attachment") { Parameters = { new NameValueHeaderValue("modification-date", @"""Wed, 12 Feb 1997 16:29:51 -0500""") } } },
+            { @"foobar", new ContentDispositionHeaderValue("foobar") }, //  @"This should be equivalent to using ""attachment""."
+            { @"attachment; example=""filename=example.txt""", new ContentDispositionHeaderValue("attachment") { Parameters = { new NameValueHeaderValue("example", @"""filename=example.txt""") } } },
+            { @"attachment; filename*=iso-8859-1''foo-%E4.html", new ContentDispositionHeaderValue("attachment") { Parameters = { new NameValueHeaderValue("filename*", "iso-8859-1''foo-%E4.html") } } }, // 'attachment', specifying a filename of foo-ä.html, using RFC2231 encoded ISO-8859-1
+            { @"attachment; filename*=UTF-8''foo-%c3%a4-%e2%82%ac.html", new ContentDispositionHeaderValue("attachment") {  Parameters = { new NameValueHeaderValue("filename*", "UTF-8''foo-%c3%a4-%e2%82%ac.html") } } }, // 'attachment', specifying a filename of foo-ä-&#x20ac;.html, using RFC2231 encoded UTF-8
+            { @"attachment; filename*=''foo-%c3%a4-%e2%82%ac.html", new ContentDispositionHeaderValue("attachment") { Parameters = { new NameValueHeaderValue("filename*", "''foo-%c3%a4-%e2%82%ac.html") } } }, // Behavior is undefined in RFC 2231, the charset part is missing, although UTF-8 was used.
+            { @"attachment; filename*=UTF-8''foo-a%22.html", new ContentDispositionHeaderValue("attachment") { FileNameStar = @"foo-a"".html" } },
+            { @"attachment; filename*= UTF-8''foo-%c3%a4.html", new ContentDispositionHeaderValue("attachment") { FileNameStar = "foo-ä.html" } },
+            { @"attachment; filename* =UTF-8''foo-%c3%a4.html", new ContentDispositionHeaderValue("attachment") { FileNameStar = "foo-ä.html" } },
+            { @"attachment; filename*=UTF-8''A-%2541.html", new ContentDispositionHeaderValue("attachment") { FileNameStar = "A-%41.html" } },
+            { @"attachment; filename*=UTF-8''%5cfoo.html", new ContentDispositionHeaderValue("attachment") { FileNameStar = @"\foo.html" } },
+            { @"attachment; filename=""foo-ae.html""; filename*=UTF-8''foo-%c3%a4.html", new ContentDispositionHeaderValue("attachment") { FileName = @"""foo-ae.html""", FileNameStar = "foo-ä.html" } },
+            { @"attachment; filename*=UTF-8''foo-%c3%a4.html; filename=""foo-ae.html""", new ContentDispositionHeaderValue("attachment") { FileNameStar = "foo-ä.html", FileName = @"""foo-ae.html""" } },
+            { @"attachment; foobar=x; filename=""foo.html""", new ContentDispositionHeaderValue("attachment") { FileName = @"""foo.html""", Parameters = { new NameValueHeaderValue("foobar", "x") } } },
+            { @"attachment; filename=""=?ISO-8859-1?Q?foo-=E4.html?=""", new ContentDispositionHeaderValue("attachment") { FileName = @"""=?ISO-8859-1?Q?foo-=E4.html?=""" } }, // attachment; filename="=?ISO-8859-1?Q?foo-=E4.html?="
+            { @"attachment; filename=""=?utf-8?B?Zm9vLeQuaHRtbA==?=""", new ContentDispositionHeaderValue("attachment") { FileName = @"""=?utf-8?B?Zm9vLeQuaHRtbA==?=""" } }, // attachment; filename="=?utf-8?B?Zm9vLeQuaHRtbA==?="
+            { @"attachment; filename=foo.html ;", new ContentDispositionHeaderValue("attachment") { FileName="foo.html" } }, // 'attachment', specifying a filename of foo.html using a token instead of a quoted-string, and adding a trailing semicolon.,
+        };
+
+        [Theory]
+        [MemberData(nameof(ValidContentDispositionTestCases))]
+        public void ContentDispositionHeaderValue_ParseValid_Success(string input, ContentDispositionHeaderValue expected)
+        {
+            // System.Diagnostics.Debugger.Launch();
+            var result = ContentDispositionHeaderValue.Parse(input);
+            Assert.Equal(expected, result);
+        }
+
+        [Theory]
+        // Invalid values
+        [InlineData(@"""inline""")] // @"'inline' only, using double quotes", false) },
+        [InlineData(@"""attachment""")] // @"'attachment' only, using double quotes", false) },
+        [InlineData(@"attachment; filename=foo bar.html")] // @"'attachment', specifying a filename of foo bar.html without using quoting.", false) },
+        // Duplicate file name parameter
+        // @"attachment; filename=""foo.html""; // filename=""bar.html""", @"'attachment', specifying two filename parameters. This is invalid syntax.", false) },
+        [InlineData(@"attachment; filename=foo[1](2).html")] // @"'attachment', specifying a filename of foo[1](2).html, but missing the quotes. Also, ""["", ""]"", ""("" and "")"" are not allowed in the HTTP <a href=""http://greenbytes.de/tech/webdav/draft-ietf-httpbis-p1-messaging-latest.html#rfc.section.1.2.2"">token</a> production.", false) },
+        [InlineData(@"attachment; filename=foo-ä.html")] // @"'attachment', specifying a filename of foo-ä.html, but missing the quotes.", false) },
+        // HTML escaping, not supported
+        // @"attachment; filename=foo-&#xc3;&#xa4;.html", // "'attachment', specifying a filename of foo-&#xc3;&#xa4;.html (which happens to be foo-ä.html using UTF-8 encoding) but missing the quotes.", false) },
+        [InlineData(@"filename=foo.html")] // @"Disposition type missing, filename specified.", false) },
+        [InlineData(@"x=y; filename=foo.html")] // @"Disposition type missing, filename specified after extension parameter.", false) },
+        [InlineData(@"""foo; filename=bar;baz""; filename=qux")] // @"Disposition type missing, filename ""qux"". Can it be more broken? (Probably)", false) },
+        [InlineData(@"filename=foo.html, filename=bar.html")] // @"Disposition type missing, two filenames specified separated by a comma (this is syntactically equivalent to have two instances of the header with one filename parameter each).", false) },
+        [InlineData(@"; filename=foo.html")] // @"Disposition type missing (but delimiter present), filename specified.", false) },
+        // This is permitted as a parameter without a value
+        // @"inline; attachment; filename=foo.html", // @"Both disposition types specified.", false) },
+        // This is permitted as a parameter without a value
+        // @"inline; attachment; filename=foo.html", // @"Both disposition types specified.", false) },
+        [InlineData(@"attachment; filename=""foo.html"".txt")] // @"'attachment', specifying a filename parameter that is broken (quoted-string followed by more characters). This is invalid syntax. ", false) },
+        [InlineData(@"attachment; filename=""bar")] // @"'attachment', specifying a filename parameter that is broken (missing ending double quote). This is invalid syntax.", false) },
+        [InlineData(@"attachment; filename=foo""bar;baz""qux")] // @"'attachment', specifying a filename parameter that is broken (disallowed characters in token syntax). This is invalid syntax.", false) },
+        [InlineData(@"attachment; filename=foo.html, attachment; filename=bar.html")] // @"'attachment', two comma-separated instances of the header field. As Content-Disposition doesn't use a list-style syntax, this is invalid syntax and, according to <a href=""http://greenbytes.de/tech/webdav/rfc2616.html#rfc.section.4.2.p.5"">RFC 2616, Section 4.2</a>, roughly equivalent to having two separate header field instances.", false) },
+        [InlineData(@"filename=foo.html; attachment")] // @"filename parameter and disposition type reversed.", false) },
+        // Escaping is not verified
+        // @"attachment; filename*=iso-8859-1''foo-%c3%a4-%e2%82%ac.html", // @"'attachment', specifying a filename of foo-ä-&#x20ac;.html, using RFC2231 encoded UTF-8, but declaring ISO-8859-1", false) },
+        // Escaping is not verified
+        // @"attachment; filename *=UTF-8''foo-%c3%a4.html", // @"'attachment', specifying a filename of foo-ä.html, using RFC2231 encoded UTF-8, with whitespace before ""*=""", false) },
+        // Escaping is not verified
+        // @"attachment; filename*=""UTF-8''foo-%c3%a4.html""", // @"'attachment', specifying a filename of foo-ä.html, using RFC2231 encoded UTF-8, with double quotes around the parameter value.", false) },
+        [InlineData(@"attachment; filename==?ISO-8859-1?Q?foo-=E4.html?=")] // @"Uses RFC 2047 style encoded word. ""="" is invalid inside the token production, so this is invalid.", false) },
+        [InlineData(@"attachment; filename==?utf-8?B?Zm9vLeQuaHRtbA==?=")] // @"Uses RFC 2047 style encoded word. ""="" is invalid inside the token production, so this is invalid.", false) },
+        public void ContentDispositionHeaderValue_ParseInvalid_Throws(string input)
+        {
+            Assert.Throws<FormatException>(() => ContentDispositionHeaderValue.Parse(input));
+        }
+
+        [Fact]
+        public void HeaderNamesWithQuotes_ExpectNamesToNotHaveQuotes()
+        {
+            var contentDispositionLine = "form-data; name =\"dotnet\"; filename=\"example.png\"";
+            var expectedName = "dotnet";
+            var expectedFileName = "example.png";
+
+            var result = ContentDispositionHeaderValue.Parse(contentDispositionLine);
+
+            Assert.Equal(expectedName, result.Name);
+            Assert.Equal(expectedFileName, result.FileName);
+        }
+
+        public class ContentDispositionValue
+        {
+            public ContentDispositionValue(string value, string description, bool valid)
+            {
+                Value = value;
+                Description = description;
+                Valid = valid;
+            }
+
+            public string Value { get; }
+
+            public string Description { get; }
+
+            public bool Valid { get; }
+        }
+
+        private void CheckValidParse(string input, ContentDispositionHeaderValue expectedResult)
+        {
+            var result = ContentDispositionHeaderValue.Parse(input);
+            Assert.Equal(expectedResult, result);
+        }
+
+        private void CheckInvalidParse(string input)
+        {
+            Assert.Throws<FormatException>(() => ContentDispositionHeaderValue.Parse(input));
+        }
+
+        private void CheckValidTryParse(string input, ContentDispositionHeaderValue expectedResult)
+        {
+            ContentDispositionHeaderValue result = null;
+            Assert.True(ContentDispositionHeaderValue.TryParse(input, out result), input);
+            Assert.Equal(expectedResult, result);
+        }
+
+        private void CheckInvalidTryParse(string input)
+        {
+            ContentDispositionHeaderValue result = null;
+            Assert.False(ContentDispositionHeaderValue.TryParse(input, out result), input);
+            Assert.Null(result);
+        }
+
+        private static void AssertFormatException(string contentDisposition)
+        {
+            Assert.Throws<FormatException>(() => new ContentDispositionHeaderValue(contentDisposition));
+        }
+    }
+}
diff --git a/src/Http/Headers/test/ContentRangeHeaderValueTest.cs b/src/Http/Headers/test/ContentRangeHeaderValueTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d8abdbdbf6b9e08098534b18f6e272aba7aadb3f
--- /dev/null
+++ b/src/Http/Headers/test/ContentRangeHeaderValueTest.cs
@@ -0,0 +1,272 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Xunit;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class ContentRangeHeaderValueTest
+    {
+        [Fact]
+        public void Ctor_LengthOnlyOverloadUseInvalidValues_Throw()
+        {
+            Assert.Throws<ArgumentOutOfRangeException>(() => new ContentRangeHeaderValue(-1));
+        }
+
+        [Fact]
+        public void Ctor_LengthOnlyOverloadValidValues_ValuesCorrectlySet()
+        {
+            var range = new ContentRangeHeaderValue(5);
+
+            Assert.False(range.HasRange, "HasRange");
+            Assert.True(range.HasLength, "HasLength");
+            Assert.Equal("bytes", range.Unit);
+            Assert.Null(range.From);
+            Assert.Null(range.To);
+            Assert.Equal(5, range.Length);
+        }
+
+        [Fact]
+        public void Ctor_FromAndToOverloadUseInvalidValues_Throw()
+        {
+            Assert.Throws<ArgumentOutOfRangeException>(() => new ContentRangeHeaderValue(-1, 1));
+            Assert.Throws<ArgumentOutOfRangeException>(() => new ContentRangeHeaderValue(0, -1));
+            Assert.Throws<ArgumentOutOfRangeException>(() => new ContentRangeHeaderValue(2, 1));
+        }
+
+        [Fact]
+        public void Ctor_FromAndToOverloadValidValues_ValuesCorrectlySet()
+        {
+            var range = new ContentRangeHeaderValue(0, 1);
+
+            Assert.True(range.HasRange, "HasRange");
+            Assert.False(range.HasLength, "HasLength");
+            Assert.Equal("bytes", range.Unit);
+            Assert.Equal(0, range.From);
+            Assert.Equal(1, range.To);
+            Assert.Null(range.Length);
+        }
+
+        [Fact]
+        public void Ctor_FromToAndLengthOverloadUseInvalidValues_Throw()
+        {
+            Assert.Throws<ArgumentOutOfRangeException>(() => new ContentRangeHeaderValue(-1, 1, 2));
+            Assert.Throws<ArgumentOutOfRangeException>(() => new ContentRangeHeaderValue(0, -1, 2));
+            Assert.Throws<ArgumentOutOfRangeException>(() => new ContentRangeHeaderValue(0, 1, -1));
+            Assert.Throws<ArgumentOutOfRangeException>(() => new ContentRangeHeaderValue(2, 1, 3));
+            Assert.Throws<ArgumentOutOfRangeException>(() => new ContentRangeHeaderValue(1, 2, 1));
+        }
+
+        [Fact]
+        public void Ctor_FromToAndLengthOverloadValidValues_ValuesCorrectlySet()
+        {
+            var range = new ContentRangeHeaderValue(0, 1, 2);
+
+            Assert.True(range.HasRange, "HasRange");
+            Assert.True(range.HasLength, "HasLength");
+            Assert.Equal("bytes", range.Unit);
+            Assert.Equal(0, range.From);
+            Assert.Equal(1, range.To);
+            Assert.Equal(2, range.Length);
+        }
+
+        [Fact]
+        public void Unit_GetAndSetValidAndInvalidValues_MatchExpectation()
+        {
+            var range = new ContentRangeHeaderValue(0);
+            range.Unit = "myunit";
+            Assert.Equal("myunit", range.Unit);
+
+            Assert.Throws<ArgumentException>(() => range.Unit = null);
+            Assert.Throws<ArgumentException>(() => range.Unit = "");
+            Assert.Throws<FormatException>(() => range.Unit = " x");
+            Assert.Throws<FormatException>(() => range.Unit = "x ");
+            Assert.Throws<FormatException>(() => range.Unit = "x y");
+        }
+
+        [Fact]
+        public void ToString_UseDifferentRanges_AllSerializedCorrectly()
+        {
+            var range = new ContentRangeHeaderValue(1, 2, 3);
+            range.Unit = "myunit";
+            Assert.Equal("myunit 1-2/3", range.ToString());
+
+            range = new ContentRangeHeaderValue(123456789012345678, 123456789012345679);
+            Assert.Equal("bytes 123456789012345678-123456789012345679/*", range.ToString());
+
+            range = new ContentRangeHeaderValue(150);
+            Assert.Equal("bytes */150", range.ToString());
+        }
+
+        [Fact]
+        public void GetHashCode_UseSameAndDifferentRanges_SameOrDifferentHashCodes()
+        {
+            var range1 = new ContentRangeHeaderValue(1, 2, 5);
+            var range2 = new ContentRangeHeaderValue(1, 2);
+            var range3 = new ContentRangeHeaderValue(5);
+            var range4 = new ContentRangeHeaderValue(1, 2, 5);
+            range4.Unit = "BYTES";
+            var range5 = new ContentRangeHeaderValue(1, 2, 5);
+            range5.Unit = "myunit";
+
+            Assert.NotEqual(range1.GetHashCode(), range2.GetHashCode());
+            Assert.NotEqual(range1.GetHashCode(), range3.GetHashCode());
+            Assert.NotEqual(range2.GetHashCode(), range3.GetHashCode());
+            Assert.Equal(range1.GetHashCode(), range4.GetHashCode());
+            Assert.NotEqual(range1.GetHashCode(), range5.GetHashCode());
+        }
+
+        [Fact]
+        public void Equals_UseSameAndDifferentRanges_EqualOrNotEqualNoExceptions()
+        {
+            var range1 = new ContentRangeHeaderValue(1, 2, 5);
+            var range2 = new ContentRangeHeaderValue(1, 2);
+            var range3 = new ContentRangeHeaderValue(5);
+            var range4 = new ContentRangeHeaderValue(1, 2, 5);
+            range4.Unit = "BYTES";
+            var range5 = new ContentRangeHeaderValue(1, 2, 5);
+            range5.Unit = "myunit";
+            var range6 = new ContentRangeHeaderValue(1, 3, 5);
+            var range7 = new ContentRangeHeaderValue(2, 2, 5);
+            var range8 = new ContentRangeHeaderValue(1, 2, 6);
+
+            Assert.False(range1.Equals(null), "bytes 1-2/5 vs. <null>");
+            Assert.False(range1.Equals(range2), "bytes 1-2/5 vs. bytes 1-2/*");
+            Assert.False(range1.Equals(range3), "bytes 1-2/5 vs. bytes */5");
+            Assert.False(range2.Equals(range3), "bytes 1-2/* vs. bytes */5");
+            Assert.True(range1.Equals(range4), "bytes 1-2/5 vs. BYTES 1-2/5");
+            Assert.True(range4.Equals(range1), "BYTES 1-2/5 vs. bytes 1-2/5");
+            Assert.False(range1.Equals(range5), "bytes 1-2/5 vs. myunit 1-2/5");
+            Assert.False(range1.Equals(range6), "bytes 1-2/5 vs. bytes 1-3/5");
+            Assert.False(range1.Equals(range7), "bytes 1-2/5 vs. bytes 2-2/5");
+            Assert.False(range1.Equals(range8), "bytes 1-2/5 vs. bytes 1-2/6");
+        }
+
+        [Fact]
+        public void Parse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            CheckValidParse(" bytes 1-2/3 ", new ContentRangeHeaderValue(1, 2, 3));
+            CheckValidParse("bytes  *  /  3", new ContentRangeHeaderValue(3));
+
+            CheckValidParse(" custom 1234567890123456789-1234567890123456799/*",
+                new ContentRangeHeaderValue(1234567890123456789, 1234567890123456799) { Unit = "custom" });
+
+            CheckValidParse(" custom * / 123 ",
+                new ContentRangeHeaderValue(123) { Unit = "custom" });
+
+            // Note that we don't have a public constructor for value 'bytes */*' since the RFC doesn't mention a
+            // scenario for it. However, if a server returns this value, we're flexible and accept it.
+            var result = ContentRangeHeaderValue.Parse("bytes */*");
+            Assert.Equal("bytes", result.Unit);
+            Assert.Null(result.From);
+            Assert.Null(result.To);
+            Assert.Null(result.Length);
+            Assert.False(result.HasRange, "HasRange");
+            Assert.False(result.HasLength, "HasLength");
+        }
+
+        [Theory]
+        [InlineData("bytes 1-2/3,")] // no character after 'length' allowed
+        [InlineData("x bytes 1-2/3")]
+        [InlineData("bytes 1-2/3.4")]
+        [InlineData(null)]
+        [InlineData("")]
+        [InlineData("bytes 3-2/5")]
+        [InlineData("bytes 6-6/5")]
+        [InlineData("bytes 1-6/5")]
+        [InlineData("bytes 1-2/")]
+        [InlineData("bytes 1-2")]
+        [InlineData("bytes 1-/")]
+        [InlineData("bytes 1-")]
+        [InlineData("bytes 1")]
+        [InlineData("bytes ")]
+        [InlineData("bytes a-2/3")]
+        [InlineData("bytes 1-b/3")]
+        [InlineData("bytes 1-2/c")]
+        [InlineData("bytes1-2/3")]
+        // More than 19 digits >>Int64.MaxValue
+        [InlineData("bytes 1-12345678901234567890/3")]
+        [InlineData("bytes 12345678901234567890-3/3")]
+        [InlineData("bytes 1-2/12345678901234567890")]
+        // Exceed Int64.MaxValue, but use 19 digits
+        [InlineData("bytes 1-9999999999999999999/3")]
+        [InlineData("bytes 9999999999999999999-3/3")]
+        [InlineData("bytes 1-2/9999999999999999999")]
+        public void Parse_SetOfInvalidValueStrings_Throws(string input)
+        {
+            Assert.Throws<FormatException>(() => ContentRangeHeaderValue.Parse(input));
+        }
+
+        [Fact]
+        public void TryParse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            CheckValidTryParse(" bytes 1-2/3 ", new ContentRangeHeaderValue(1, 2, 3));
+            CheckValidTryParse("bytes  *  /  3", new ContentRangeHeaderValue(3));
+
+            CheckValidTryParse(" custom 1234567890123456789-1234567890123456799/*",
+                new ContentRangeHeaderValue(1234567890123456789, 1234567890123456799) { Unit = "custom" });
+
+            CheckValidTryParse(" custom * / 123 ",
+                new ContentRangeHeaderValue(123) { Unit = "custom" });
+
+            // Note that we don't have a public constructor for value 'bytes */*' since the RFC doesn't mention a
+            // scenario for it. However, if a server returns this value, we're flexible and accept it.
+            ContentRangeHeaderValue result = null;
+            Assert.True(ContentRangeHeaderValue.TryParse("bytes */*", out result));
+            Assert.Equal("bytes", result.Unit);
+            Assert.Null(result.From);
+            Assert.Null(result.To);
+            Assert.Null(result.Length);
+            Assert.False(result.HasRange, "HasRange");
+            Assert.False(result.HasLength, "HasLength");
+        }
+
+        [Theory]
+        [InlineData("bytes 1-2/3,")] // no character after 'length' allowed
+        [InlineData("x bytes 1-2/3")]
+        [InlineData("bytes 1-2/3.4")]
+        [InlineData(null)]
+        [InlineData("")]
+        [InlineData("bytes 3-2/5")]
+        [InlineData("bytes 6-6/5")]
+        [InlineData("bytes 1-6/5")]
+        [InlineData("bytes 1-2/")]
+        [InlineData("bytes 1-2")]
+        [InlineData("bytes 1-/")]
+        [InlineData("bytes 1-")]
+        [InlineData("bytes 1")]
+        [InlineData("bytes ")]
+        [InlineData("bytes a-2/3")]
+        [InlineData("bytes 1-b/3")]
+        [InlineData("bytes 1-2/c")]
+        [InlineData("bytes1-2/3")]
+        // More than 19 digits >>Int64.MaxValue
+        [InlineData("bytes 1-12345678901234567890/3")]
+        [InlineData("bytes 12345678901234567890-3/3")]
+        [InlineData("bytes 1-2/12345678901234567890")]
+        // Exceed Int64.MaxValue, but use 19 digits
+        [InlineData("bytes 1-9999999999999999999/3")]
+        [InlineData("bytes 9999999999999999999-3/3")]
+        [InlineData("bytes 1-2/9999999999999999999")]
+        public void TryParse_SetOfInvalidValueStrings_ReturnsFalse(string input)
+        {
+            ContentRangeHeaderValue result = null;
+            Assert.False(ContentRangeHeaderValue.TryParse(input, out result));
+            Assert.Null(result);
+        }
+
+        private void CheckValidParse(string input, ContentRangeHeaderValue expectedResult)
+        {
+            var result = ContentRangeHeaderValue.Parse(input);
+            Assert.Equal(expectedResult, result);
+        }
+
+        private void CheckValidTryParse(string input, ContentRangeHeaderValue expectedResult)
+        {
+            ContentRangeHeaderValue result = null;
+            Assert.True(ContentRangeHeaderValue.TryParse(input, out result));
+            Assert.Equal(expectedResult, result);
+        }
+    }
+}
diff --git a/src/Http/Headers/test/CookieHeaderValueTest.cs b/src/Http/Headers/test/CookieHeaderValueTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..416441991d7cc15abfdb6535d8e01b75ca18f676
--- /dev/null
+++ b/src/Http/Headers/test/CookieHeaderValueTest.cs
@@ -0,0 +1,326 @@
+// 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 Xunit;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class CookieHeaderValueTest
+    {
+        public static TheoryData<CookieHeaderValue, string> CookieHeaderDataSet
+        {
+            get
+            {
+                var dataset = new TheoryData<CookieHeaderValue, string>();
+                var header1 = new CookieHeaderValue("name1", "n1=v1&n2=v2&n3=v3");
+                dataset.Add(header1, "name1=n1=v1&n2=v2&n3=v3");
+
+                var header2 = new CookieHeaderValue("name2", "");
+                dataset.Add(header2, "name2=");
+
+                var header3 = new CookieHeaderValue("name3", "value3");
+                dataset.Add(header3, "name3=value3");
+
+                var header4 = new CookieHeaderValue("name4", "\"value4\"");
+                dataset.Add(header4, "name4=\"value4\"");
+
+                return dataset;
+            }
+        }
+
+        public static TheoryData<string> InvalidCookieHeaderDataSet
+        {
+            get
+            {
+                return new TheoryData<string>
+                {
+                    "=value",
+                    "name=value;",
+                    "name=value,",
+                };
+            }
+        }
+
+        public static TheoryData<string> InvalidCookieNames
+        {
+            get
+            {
+                return new TheoryData<string>
+                {
+                    "<acb>",
+                    "{acb}",
+                    "[acb]",
+                    "\"acb\"",
+                    "a,b",
+                    "a;b",
+                    "a\\b",
+                    "a b",
+                };
+            }
+        }
+
+        public static TheoryData<string> InvalidCookieValues
+        {
+            get
+            {
+                return new TheoryData<string>
+                {
+                    { "\"" },
+                    { "a,b" },
+                    { "a;b" },
+                    { "a\\b" },
+                    { "\"abc" },
+                    { "a\"bc" },
+                    { "abc\"" },
+                    { "a b" },
+                };
+            }
+        }
+
+        public static TheoryData<IList<CookieHeaderValue>, string[]> ListOfCookieHeaderDataSet
+        {
+            get
+            {
+                var dataset = new TheoryData<IList<CookieHeaderValue>, string[]>();
+                var header1 = new CookieHeaderValue("name1", "n1=v1&n2=v2&n3=v3");
+                var string1 = "name1=n1=v1&n2=v2&n3=v3";
+
+                var header2 = new CookieHeaderValue("name2", "value2");
+                var string2 = "name2=value2";
+
+                var header3 = new CookieHeaderValue("name3", "value3");
+                var string3 = "name3=value3";
+
+                var header4 = new CookieHeaderValue("name4", "\"value4\"");
+                var string4 = "name4=\"value4\"";
+
+                dataset.Add(new[] { header1 }.ToList(), new[] { string1 });
+                dataset.Add(new[] { header1, header1 }.ToList(), new[] { string1, string1 });
+                dataset.Add(new[] { header1, header1 }.ToList(), new[] { string1, null, "", " ", ";", " , ", string1 });
+                dataset.Add(new[] { header2 }.ToList(), new[] { string2 });
+                dataset.Add(new[] { header1, header2 }.ToList(), new[] { string1, string2 });
+                dataset.Add(new[] { header1, header2 }.ToList(), new[] { string1 + ", " + string2 });
+                dataset.Add(new[] { header2, header1 }.ToList(), new[] { string2 + "; " + string1 });
+                dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string1, string2, string3, string4 });
+                dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(",", string1, string2, string3, string4) });
+                dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(";", string1, string2, string3, string4) });
+
+                return dataset;
+            }
+        }
+
+        public static TheoryData<IList<CookieHeaderValue>, string[]> ListWithInvalidCookieHeaderDataSet
+        {
+            get
+            {
+                var dataset = new TheoryData<IList<CookieHeaderValue>, string[]>();
+                var header1 = new CookieHeaderValue("name1", "n1=v1&n2=v2&n3=v3");
+                var validString1 = "name1=n1=v1&n2=v2&n3=v3";
+
+                var header2 = new CookieHeaderValue("name2", "value2");
+                var validString2 = "name2=value2";
+
+                var header3 = new CookieHeaderValue("name3", "value3");
+                var validString3 = "name3=value3";
+
+                var invalidString1 = "ipt={\"v\":{\"L\":3},\"pt\":{\"d\":3},ct\":{},\"_t\":44,\"_v\":\"2\"}";
+
+                dataset.Add(null, new[] { invalidString1 });
+                dataset.Add(new[] { header1 }.ToList(), new[] { validString1, invalidString1 });
+                dataset.Add(new[] { header1 }.ToList(), new[] { validString1, null, "", " ", ";", " , ", invalidString1 });
+                dataset.Add(new[] { header1 }.ToList(), new[] { invalidString1, null, "", " ", ";", " , ", validString1 });
+                dataset.Add(new[] { header1 }.ToList(), new[] { validString1 + ", " + invalidString1 });
+                dataset.Add(new[] { header2 }.ToList(), new[] { invalidString1 + ", " + validString2 });
+                dataset.Add(new[] { header1 }.ToList(), new[] { invalidString1 + "; " + validString1 });
+                dataset.Add(new[] { header2 }.ToList(), new[] { validString2 + "; " + invalidString1 });
+                dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { invalidString1, validString1, validString2, validString3  });
+                dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { validString1, invalidString1, validString2, validString3 });
+                dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { validString1, validString2, invalidString1, validString3 });
+                dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { validString1, validString2, validString3, invalidString1 });
+                dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(",", invalidString1, validString1, validString2, validString3) });
+                dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(",", validString1, invalidString1, validString2, validString3) });
+                dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(",", validString1, validString2, invalidString1, validString3) });
+                dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(",", validString1, validString2, validString3, invalidString1) });
+                dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(";", invalidString1, validString1, validString2, validString3) });
+                dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(";", validString1, invalidString1, validString2, validString3) });
+                dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(";", validString1, validString2, invalidString1, validString3) });
+                dataset.Add(new[] { header1, header2, header3 }.ToList(), new[] { string.Join(";", validString1, validString2, validString3, invalidString1) });
+
+                return dataset;
+            }
+        }
+
+        [Fact]
+        public void CookieHeaderValue_CtorThrowsOnNullName()
+        {
+            Assert.Throws<ArgumentNullException>(() => new CookieHeaderValue(null, "value"));
+        }
+
+        [Theory]
+        [MemberData(nameof(InvalidCookieNames))]
+        public void CookieHeaderValue_CtorThrowsOnInvalidName(string name)
+        {
+            Assert.Throws<ArgumentException>(() => new CookieHeaderValue(name, "value"));
+        }
+
+        [Theory]
+        [MemberData(nameof(InvalidCookieValues))]
+        public void CookieHeaderValue_CtorThrowsOnInvalidValue(string value)
+        {
+            Assert.Throws<ArgumentException>(() => new CookieHeaderValue("name", value));
+        }
+
+        [Fact]
+        public void CookieHeaderValue_Ctor1_InitializesCorrectly()
+        {
+            var header = new CookieHeaderValue("cookie");
+            Assert.Equal("cookie", header.Name);
+            Assert.Equal(string.Empty, header.Value);
+        }
+
+        [Theory]
+        [InlineData("name", "")]
+        [InlineData("name", "value")]
+        [InlineData("name", "\"acb\"")]
+        public void CookieHeaderValue_Ctor2InitializesCorrectly(string name, string value)
+        {
+            var header = new CookieHeaderValue(name, value);
+            Assert.Equal(name, header.Name);
+            Assert.Equal(value, header.Value);
+        }
+
+        [Fact]
+        public void CookieHeaderValue_Value()
+        {
+            var cookie = new CookieHeaderValue("name");
+            Assert.Equal(string.Empty, cookie.Value);
+
+            cookie.Value = "value1";
+            Assert.Equal("value1", cookie.Value);
+        }
+
+        [Theory]
+        [MemberData(nameof(CookieHeaderDataSet))]
+        public void CookieHeaderValue_ToString(CookieHeaderValue input, string expectedValue)
+        {
+            Assert.Equal(expectedValue, input.ToString());
+        }
+
+        [Theory]
+        [MemberData(nameof(CookieHeaderDataSet))]
+        public void CookieHeaderValue_Parse_AcceptsValidValues(CookieHeaderValue cookie, string expectedValue)
+        {
+            var header = CookieHeaderValue.Parse(expectedValue);
+
+            Assert.Equal(cookie, header);
+            Assert.Equal(expectedValue, header.ToString());
+        }
+
+        [Theory]
+        [MemberData(nameof(CookieHeaderDataSet))]
+        public void CookieHeaderValue_TryParse_AcceptsValidValues(CookieHeaderValue cookie, string expectedValue)
+        {
+            Assert.True(CookieHeaderValue.TryParse(expectedValue, out var header));
+
+            Assert.Equal(cookie, header);
+            Assert.Equal(expectedValue, header.ToString());
+        }
+
+        [Theory]
+        [MemberData(nameof(InvalidCookieHeaderDataSet))]
+        public void CookieHeaderValue_Parse_RejectsInvalidValues(string value)
+        {
+            Assert.Throws<FormatException>(() => CookieHeaderValue.Parse(value));
+        }
+
+        [Theory]
+        [MemberData(nameof(InvalidCookieHeaderDataSet))]
+        public void CookieHeaderValue_TryParse_RejectsInvalidValues(string value)
+        {
+            Assert.False(CookieHeaderValue.TryParse(value, out var _));
+        }
+
+        [Theory]
+        [MemberData(nameof(ListOfCookieHeaderDataSet))]
+        public void CookieHeaderValue_ParseList_AcceptsValidValues(IList<CookieHeaderValue> cookies, string[] input)
+        {
+            var results = CookieHeaderValue.ParseList(input);
+
+            Assert.Equal(cookies, results);
+        }
+
+        [Theory]
+        [MemberData(nameof(ListOfCookieHeaderDataSet))]
+        public void CookieHeaderValue_ParseStrictList_AcceptsValidValues(IList<CookieHeaderValue> cookies, string[] input)
+        {
+            var results = CookieHeaderValue.ParseStrictList(input);
+
+            Assert.Equal(cookies, results);
+        }
+
+        [Theory]
+        [MemberData(nameof(ListOfCookieHeaderDataSet))]
+        public void CookieHeaderValue_TryParseList_AcceptsValidValues(IList<CookieHeaderValue> cookies, string[] input)
+        {
+            var result = CookieHeaderValue.TryParseList(input, out var results);
+            Assert.True(result);
+
+            Assert.Equal(cookies, results);
+        }
+
+        [Theory]
+        [MemberData(nameof(ListOfCookieHeaderDataSet))]
+        public void CookieHeaderValue_TryParseStrictList_AcceptsValidValues(IList<CookieHeaderValue> cookies, string[] input)
+        {
+            var result = CookieHeaderValue.TryParseStrictList(input, out var results);
+            Assert.True(result);
+
+            Assert.Equal(cookies, results);
+        }
+
+        [Theory]
+        [MemberData(nameof(ListWithInvalidCookieHeaderDataSet))]
+        public void CookieHeaderValue_ParseList_ExcludesInvalidValues(IList<CookieHeaderValue> cookies, string[] input)
+        {
+            var results = CookieHeaderValue.ParseList(input);
+            // ParseList aways returns a list, even if empty. TryParseList may return null (via out).
+            Assert.Equal(cookies ?? new List<CookieHeaderValue>(), results);
+        }
+
+        [Theory]
+        [MemberData(nameof(ListWithInvalidCookieHeaderDataSet))]
+        public void CookieHeaderValue_TryParseList_ExcludesInvalidValues(IList<CookieHeaderValue> cookies, string[] input)
+        {
+            var result = CookieHeaderValue.TryParseList(input, out var results);
+            Assert.Equal(cookies, results);
+            Assert.Equal(cookies?.Count > 0, result);
+        }
+
+        [Theory]
+        [MemberData(nameof(ListWithInvalidCookieHeaderDataSet))]
+        public void CookieHeaderValue_ParseStrictList_ThrowsForAnyInvalidValues(
+#pragma warning disable xUnit1026 // Theory methods should use all of their parameters
+            IList<CookieHeaderValue> cookies,
+#pragma warning restore xUnit1026 // Theory methods should use all of their parameters
+            string[] input)
+        {
+            Assert.Throws<FormatException>(() => CookieHeaderValue.ParseStrictList(input));
+        }
+
+        [Theory]
+        [MemberData(nameof(ListWithInvalidCookieHeaderDataSet))]
+        public void CookieHeaderValue_TryParseStrictList_FailsForAnyInvalidValues(
+#pragma warning disable xUnit1026 // Theory methods should use all of their parameters
+            IList<CookieHeaderValue> cookies, 
+#pragma warning restore xUnit1026 // Theory methods should use all of their parameters
+            string[] input)
+        {
+            var result = CookieHeaderValue.TryParseStrictList(input, out var results);
+            Assert.Null(results);
+            Assert.False(result);
+        }
+    }
+}
diff --git a/src/Http/Headers/test/DateParserTest.cs b/src/Http/Headers/test/DateParserTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5c211c4368446376cf9382eadafebaec997d9cbf
--- /dev/null
+++ b/src/Http/Headers/test/DateParserTest.cs
@@ -0,0 +1,56 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Xunit;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class DateParserTest
+    {
+        [Theory]
+        [MemberData(nameof(ValidStringData))]
+        public void TryParse_SetOfValidValueStrings_ParsedCorrectly(string input, DateTimeOffset expected)
+        {
+            // We don't need to validate all possible date values, since they're already tested in HttpRuleParserTest.
+            // Just make sure the parser calls HttpRuleParser methods correctly.
+            Assert.True(HeaderUtilities.TryParseDate(input, out var result));
+            Assert.Equal(expected, result);
+        }
+
+        public static IEnumerable<object[]> ValidStringData()
+        {
+            yield return new object[] { "Tue, 15 Nov 1994 08:12:31 GMT", new DateTimeOffset(1994, 11, 15, 8, 12, 31, TimeSpan.Zero) };
+            yield return new object[] { "      Sunday, 06-Nov-94 08:49:37 GMT   ", new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero) };
+            yield return new object[] { " Tue,\r\n 15 Nov\r\n 1994 08:12:31 GMT   ", new DateTimeOffset(1994, 11, 15, 8, 12, 31, TimeSpan.Zero) };
+            yield return new object[] { "Sat, 09-Dec-2017 07:07:03 GMT ", new DateTimeOffset(2017, 12, 09, 7, 7, 3, TimeSpan.Zero) };
+        }
+
+        [Theory]
+        [MemberData(nameof(InvalidStringData))]
+        public void TryParse_SetOfInvalidValueStrings_ReturnsFalse(string input)
+        {
+            Assert.False(HeaderUtilities.TryParseDate(input, out var result));
+            Assert.Equal(new DateTimeOffset(), result);
+        }
+
+        public static IEnumerable<object[]> InvalidStringData()
+        {
+            yield return new object[] { null };
+            yield return new object[] { string.Empty };
+            yield return new object[] { "  " };
+            yield return new object[] { "!!Sunday, 06-Nov-94 08:49:37 GMT" };
+        }
+
+        [Fact]
+        public void ToString_UseDifferentValues_MatchExpectation()
+        {
+            Assert.Equal("Sat, 31 Jul 2010 15:38:57 GMT",
+                HeaderUtilities.FormatDate(new DateTimeOffset(2010, 7, 31, 15, 38, 57, TimeSpan.Zero)));
+
+            Assert.Equal("Fri, 01 Jan 2010 01:01:01 GMT",
+                HeaderUtilities.FormatDate(new DateTimeOffset(2010, 1, 1, 1, 1, 1, TimeSpan.Zero)));
+        }
+    }
+}
diff --git a/src/Http/Headers/test/EntityTagHeaderValueTest.cs b/src/Http/Headers/test/EntityTagHeaderValueTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f633fec2264f1212b67939d1d918ef57c402feef
--- /dev/null
+++ b/src/Http/Headers/test/EntityTagHeaderValueTest.cs
@@ -0,0 +1,533 @@
+// 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 Xunit;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class EntityTagHeaderValueTest
+    {
+        [Fact]
+        public void Ctor_ETagNull_Throw()
+        {
+            Assert.Throws<ArgumentException>(() => new EntityTagHeaderValue(null));
+            // null and empty should be treated the same. So we also throw for empty strings.
+            Assert.Throws<ArgumentException>(() => new EntityTagHeaderValue(string.Empty));
+        }
+
+        [Fact]
+        public void Ctor_ETagInvalidFormat_ThrowFormatException()
+        {
+            // When adding values using strongly typed objects, no leading/trailing LWS (whitespaces) are allowed.
+            AssertFormatException("tag");
+            AssertFormatException(" tag ");
+            AssertFormatException("\"tag\" invalid");
+            AssertFormatException("\"tag");
+            AssertFormatException("tag\"");
+            AssertFormatException("\"tag\"\"");
+            AssertFormatException("\"\"tag\"\"");
+            AssertFormatException("\"\"tag\"");
+            AssertFormatException("W/\"tag\""); // tag value must not contain 'W/'
+        }
+
+        [Fact]
+        public void Ctor_ETagValidFormat_SuccessfullyCreated()
+        {
+            var etag = new EntityTagHeaderValue("\"tag\"");
+            Assert.Equal("\"tag\"", etag.Tag);
+            Assert.False(etag.IsWeak, "IsWeak");
+        }
+
+        [Fact]
+        public void Ctor_ETagValidFormatAndIsWeak_SuccessfullyCreated()
+        {
+            var etag = new EntityTagHeaderValue("\"e tag\"", true);
+            Assert.Equal("\"e tag\"", etag.Tag);
+            Assert.True(etag.IsWeak, "IsWeak");
+        }
+
+        [Fact]
+        public void ToString_UseDifferentETags_AllSerializedCorrectly()
+        {
+            var etag = new EntityTagHeaderValue("\"e tag\"");
+            Assert.Equal("\"e tag\"", etag.ToString());
+
+            etag = new EntityTagHeaderValue("\"e tag\"", true);
+            Assert.Equal("W/\"e tag\"", etag.ToString());
+
+            etag = new EntityTagHeaderValue("\"\"", false);
+            Assert.Equal("\"\"", etag.ToString());
+        }
+
+        [Fact]
+        public void GetHashCode_UseSameAndDifferentETags_SameOrDifferentHashCodes()
+        {
+            var etag1 = new EntityTagHeaderValue("\"tag\"");
+            var etag2 = new EntityTagHeaderValue("\"TAG\"");
+            var etag3 = new EntityTagHeaderValue("\"tag\"", true);
+            var etag4 = new EntityTagHeaderValue("\"tag1\"");
+            var etag5 = new EntityTagHeaderValue("\"tag\"");
+            var etag6 = EntityTagHeaderValue.Any;
+
+            Assert.NotEqual(etag1.GetHashCode(), etag2.GetHashCode());
+            Assert.NotEqual(etag1.GetHashCode(), etag3.GetHashCode());
+            Assert.NotEqual(etag1.GetHashCode(), etag4.GetHashCode());
+            Assert.NotEqual(etag1.GetHashCode(), etag6.GetHashCode());
+            Assert.Equal(etag1.GetHashCode(), etag5.GetHashCode());
+        }
+
+        [Fact]
+        public void Equals_UseSameAndDifferentETags_EqualOrNotEqualNoExceptions()
+        {
+            var etag1 = new EntityTagHeaderValue("\"tag\"");
+            var etag2 = new EntityTagHeaderValue("\"TAG\"");
+            var etag3 = new EntityTagHeaderValue("\"tag\"", true);
+            var etag4 = new EntityTagHeaderValue("\"tag1\"");
+            var etag5 = new EntityTagHeaderValue("\"tag\"");
+            var etag6 = EntityTagHeaderValue.Any;
+
+            Assert.False(etag1.Equals(etag2), "Different casing.");
+            Assert.False(etag2.Equals(etag1), "Different casing.");
+            Assert.False(etag1.Equals(null), "tag vs. <null>.");
+            Assert.False(etag1.Equals(etag3), "strong vs. weak.");
+            Assert.False(etag3.Equals(etag1), "weak vs. strong.");
+            Assert.False(etag1.Equals(etag4), "tag vs. tag1.");
+            Assert.False(etag1.Equals(etag6), "tag vs. *.");
+            Assert.True(etag1.Equals(etag5), "tag vs. tag..");
+        }
+
+        [Fact]
+        public void Compare_WithNull_ReturnsFalse()
+        {
+            Assert.False(EntityTagHeaderValue.Any.Compare(null, useStrongComparison: true));
+            Assert.False(EntityTagHeaderValue.Any.Compare(null, useStrongComparison: false));
+        }
+
+        public static TheoryData<EntityTagHeaderValue, EntityTagHeaderValue> NotEquivalentUnderStrongComparison
+        {
+            get
+            {
+                return new TheoryData<EntityTagHeaderValue, EntityTagHeaderValue>
+                {
+                    { new EntityTagHeaderValue("\"tag\""), new EntityTagHeaderValue("\"TAG\"") },
+                    { new EntityTagHeaderValue("\"tag\"", true), new EntityTagHeaderValue("\"tag\"", true) },
+                    { new EntityTagHeaderValue("\"tag\""), new EntityTagHeaderValue("\"tag\"", true) },
+                    { new EntityTagHeaderValue("\"tag\""), new EntityTagHeaderValue("\"tag1\"") },
+                    { new EntityTagHeaderValue("\"tag\""), EntityTagHeaderValue.Any },
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(NotEquivalentUnderStrongComparison))]
+        public void CompareUsingStrongComparison_NonEquivalentPairs_ReturnFalse(EntityTagHeaderValue left, EntityTagHeaderValue right)
+        {
+            Assert.False(left.Compare(right, useStrongComparison: true));
+            Assert.False(right.Compare(left, useStrongComparison: true));
+        }
+
+        public static TheoryData<EntityTagHeaderValue, EntityTagHeaderValue> EquivalentUnderStrongComparison
+        {
+            get
+            {
+                return new TheoryData<EntityTagHeaderValue, EntityTagHeaderValue>
+                {
+                    { new EntityTagHeaderValue("\"tag\""), new EntityTagHeaderValue("\"tag\"") },
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(EquivalentUnderStrongComparison))]
+        public void CompareUsingStrongComparison_EquivalentPairs_ReturnTrue(EntityTagHeaderValue left, EntityTagHeaderValue right)
+        {
+            Assert.True(left.Compare(right, useStrongComparison: true));
+            Assert.True(right.Compare(left, useStrongComparison: true));
+        }
+
+        public static TheoryData<EntityTagHeaderValue, EntityTagHeaderValue> NotEquivalentUnderWeakComparison
+        {
+            get
+            {
+                return new TheoryData<EntityTagHeaderValue, EntityTagHeaderValue>
+                {
+                    { new EntityTagHeaderValue("\"tag\""), new EntityTagHeaderValue("\"TAG\"") },
+                    { new EntityTagHeaderValue("\"tag\""), new EntityTagHeaderValue("\"tag1\"") },
+                    { new EntityTagHeaderValue("\"tag\""), EntityTagHeaderValue.Any },
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(NotEquivalentUnderWeakComparison))]
+        public void CompareUsingWeakComparison_NonEquivalentPairs_ReturnFalse(EntityTagHeaderValue left, EntityTagHeaderValue right)
+        {
+            Assert.False(left.Compare(right, useStrongComparison: false));
+            Assert.False(right.Compare(left, useStrongComparison: false));
+        }
+
+        public static TheoryData<EntityTagHeaderValue, EntityTagHeaderValue> EquivalentUnderWeakComparison
+        {
+            get
+            {
+                return new TheoryData<EntityTagHeaderValue, EntityTagHeaderValue>
+                {
+                    { new EntityTagHeaderValue("\"tag\""), new EntityTagHeaderValue("\"tag\"") },
+                    { new EntityTagHeaderValue("\"tag\"", true), new EntityTagHeaderValue("\"tag\"", true) },
+                    { new EntityTagHeaderValue("\"tag\""), new EntityTagHeaderValue("\"tag\"", true) },
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(EquivalentUnderWeakComparison))]
+        public void CompareUsingWeakComparison_EquivalentPairs_ReturnTrue(EntityTagHeaderValue left, EntityTagHeaderValue right)
+        {
+            Assert.True(left.Compare(right, useStrongComparison: false));
+            Assert.True(right.Compare(left, useStrongComparison: false));
+        }
+
+        [Fact]
+        public void Parse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            CheckValidParse("\"tag\"", new EntityTagHeaderValue("\"tag\""));
+            CheckValidParse(" \"tag\" ", new EntityTagHeaderValue("\"tag\""));
+            CheckValidParse("\r\n \"tag\"\r\n ", new EntityTagHeaderValue("\"tag\""));
+            CheckValidParse("\"tag\"", new EntityTagHeaderValue("\"tag\""));
+            CheckValidParse("\"tag会\"", new EntityTagHeaderValue("\"tag会\""));
+            CheckValidParse("W/\"tag\"", new EntityTagHeaderValue("\"tag\"", true));
+            CheckValidParse("*", new EntityTagHeaderValue("*"));
+        }
+
+        [Fact]
+        public void Parse_SetOfInvalidValueStrings_Throws()
+        {
+            CheckInvalidParse(null);
+            CheckInvalidParse(string.Empty);
+            CheckInvalidParse("  ");
+            CheckInvalidParse("  !");
+            CheckInvalidParse("tag\"  !");
+            CheckInvalidParse("!\"tag\"");
+            CheckInvalidParse("\"tag\",");
+            CheckInvalidParse("W");
+            CheckInvalidParse("W/");
+            CheckInvalidParse("W/\"");
+            CheckInvalidParse("\"tag\" \"tag2\"");
+            CheckInvalidParse("/\"tag\"");
+        }
+
+        [Fact]
+        public void TryParse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            CheckValidTryParse("\"tag\"", new EntityTagHeaderValue("\"tag\""));
+            CheckValidTryParse(" \"tag\" ", new EntityTagHeaderValue("\"tag\""));
+            CheckValidTryParse("\r\n \"tag\"\r\n ", new EntityTagHeaderValue("\"tag\""));
+            CheckValidTryParse("\"tag\"", new EntityTagHeaderValue("\"tag\""));
+            CheckValidTryParse("\"tag会\"", new EntityTagHeaderValue("\"tag会\""));
+            CheckValidTryParse("W/\"tag\"", new EntityTagHeaderValue("\"tag\"", true));
+            CheckValidTryParse("*", new EntityTagHeaderValue("*"));
+        }
+
+        [Fact]
+        public void TryParse_SetOfInvalidValueStrings_ReturnsFalse()
+        {
+            CheckInvalidTryParse(null);
+            CheckInvalidTryParse(string.Empty);
+            CheckInvalidTryParse("  ");
+            CheckInvalidTryParse("  !");
+            CheckInvalidTryParse("tag\"  !");
+            CheckInvalidTryParse("!\"tag\"");
+            CheckInvalidTryParse("\"tag\",");
+            CheckInvalidTryParse("\"tag\" \"tag2\"");
+            CheckInvalidTryParse("/\"tag\"");
+        }
+
+        [Fact]
+        public void ParseList_NullOrEmptyArray_ReturnsEmptyList()
+        {
+            var result = EntityTagHeaderValue.ParseList(null);
+            Assert.NotNull(result);
+            Assert.Equal(0, result.Count);
+
+            result = EntityTagHeaderValue.ParseList(new string[0]);
+            Assert.NotNull(result);
+            Assert.Equal(0, result.Count);
+
+            result = EntityTagHeaderValue.ParseList(new string[] { "" });
+            Assert.NotNull(result);
+            Assert.Equal(0, result.Count);
+        }
+
+        [Fact]
+        public void TryParseList_NullOrEmptyArray_ReturnsFalse()
+        {
+            IList<EntityTagHeaderValue> results = null;
+            Assert.False(EntityTagHeaderValue.TryParseList(null, out results));
+            Assert.False(EntityTagHeaderValue.TryParseList(new string[0], out results));
+            Assert.False(EntityTagHeaderValue.TryParseList(new string[] { "" }, out results));
+        }
+
+        [Fact]
+        public void ParseList_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            var inputs = new[]
+            {
+                "",
+                "\"tag\"",
+                "",
+                " \"tag\" ",
+                "\r\n \"tag\"\r\n ",
+                "\"tag会\"",
+                "\"tag\",\"tag\"",
+                "\"tag\", \"tag\"",
+                "W/\"tag\"",
+            };
+            IList<EntityTagHeaderValue> results = EntityTagHeaderValue.ParseList(inputs);
+
+            var expectedResults = new[]
+            {
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag会\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\"", true),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void ParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            var inputs = new[]
+            {
+                "",
+                "\"tag\"",
+                "",
+                " \"tag\" ",
+                "\r\n \"tag\"\r\n ",
+                "\"tag会\"",
+                "\"tag\",\"tag\"",
+                "\"tag\", \"tag\"",
+                "W/\"tag\"",
+            };
+            IList<EntityTagHeaderValue> results = EntityTagHeaderValue.ParseStrictList(inputs);
+
+            var expectedResults = new[]
+            {
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag会\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\"", true),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void TryParseList_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            var inputs = new[]
+            {
+                "",
+                "\"tag\"",
+                "",
+                " \"tag\" ",
+                "\r\n \"tag\"\r\n ",
+                "\"tag会\"",
+                "\"tag\",\"tag\"",
+                "\"tag\", \"tag\"",
+                "W/\"tag\"",
+            };
+            IList<EntityTagHeaderValue> results;
+            Assert.True(EntityTagHeaderValue.TryParseList(inputs, out results));
+            var expectedResults = new[]
+            {
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag会\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\"", true),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void TryParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            var inputs = new[]
+            {
+                "",
+                "\"tag\"",
+                "",
+                " \"tag\" ",
+                "\r\n \"tag\"\r\n ",
+                "\"tag会\"",
+                "\"tag\",\"tag\"",
+                "\"tag\", \"tag\"",
+                "W/\"tag\"",
+            };
+            IList<EntityTagHeaderValue> results;
+            Assert.True(EntityTagHeaderValue.TryParseStrictList(inputs, out results));
+            var expectedResults = new[]
+            {
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag会\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\"", true),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void ParseList_WithSomeInvlaidValues_ExcludesInvalidValues()
+        {
+            var inputs = new[]
+            {
+                "",
+                "\"tag\", tag, \"tag\"",
+                "tag, \"tag\"",
+                "",
+                " \"tag ",
+                "\r\n tag\"\r\n ",
+                "\"tag会\"",
+                "\"tag\", \"tag\"",
+                "W/\"tag\"",
+            };
+            var results = EntityTagHeaderValue.ParseList(inputs);
+            var expectedResults = new[]
+            {
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag会\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\"", true),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void ParseStrictList_WithSomeInvlaidValues_Throws()
+        {
+            var inputs = new[]
+            {
+                "",
+                "\"tag\", tag, \"tag\"",
+                "tag, \"tag\"",
+                "",
+                " \"tag ",
+                "\r\n tag\"\r\n ",
+                "\"tag会\"",
+                "\"tag\", \"tag\"",
+                "W/\"tag\"",
+            };
+            Assert.Throws<FormatException>(() => EntityTagHeaderValue.ParseStrictList(inputs));
+        }
+
+        [Fact]
+        public void TryParseList_WithSomeInvlaidValues_ExcludesInvalidValues()
+        {
+            var inputs = new[]
+            {
+                "",
+                "\"tag\", tag, \"tag\"",
+                "tag, \"tag\"",
+                "",
+                " \"tag ",
+                "\r\n tag\"\r\n ",
+                "\"tag会\"",
+                "\"tag\", \"tag\"",
+                "W/\"tag\"",
+            };
+            IList<EntityTagHeaderValue> results;
+            Assert.True(EntityTagHeaderValue.TryParseList(inputs, out results));
+            var expectedResults = new[]
+            {
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag会\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\""),
+                new EntityTagHeaderValue("\"tag\"", true),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void TryParseStrictList_WithSomeInvlaidValues_ReturnsFalse()
+        {
+            var inputs = new[]
+            {
+                "",
+                "\"tag\", tag, \"tag\"",
+                "tag, \"tag\"",
+                "",
+                " \"tag ",
+                "\r\n tag\"\r\n ",
+                "\"tag会\"",
+                "\"tag\", \"tag\"",
+                "W/\"tag\"",
+            };
+            IList<EntityTagHeaderValue> results;
+            Assert.False(EntityTagHeaderValue.TryParseStrictList(inputs, out results));
+        }
+
+        private void CheckValidParse(string input, EntityTagHeaderValue expectedResult)
+        {
+            var result = EntityTagHeaderValue.Parse(input);
+            Assert.Equal(expectedResult, result);
+        }
+
+        private void CheckInvalidParse(string input)
+        {
+            Assert.Throws<FormatException>(() => EntityTagHeaderValue.Parse(input));
+        }
+
+        private void CheckValidTryParse(string input, EntityTagHeaderValue expectedResult)
+        {
+            EntityTagHeaderValue result = null;
+            Assert.True(EntityTagHeaderValue.TryParse(input, out result));
+            Assert.Equal(expectedResult, result);
+        }
+
+        private void CheckInvalidTryParse(string input)
+        {
+            EntityTagHeaderValue result = null;
+            Assert.False(EntityTagHeaderValue.TryParse(input, out result));
+            Assert.Null(result);
+        }
+
+        private static void AssertFormatException(string tag)
+        {
+            Assert.Throws<FormatException>(() => new EntityTagHeaderValue(tag));
+        }
+    }
+}
diff --git a/src/Http/Headers/test/HeaderUtilitiesTest.cs b/src/Http/Headers/test/HeaderUtilitiesTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..848190b02e7b22648474081e8a60cea70c6c1c6e
--- /dev/null
+++ b/src/Http/Headers/test/HeaderUtilitiesTest.cs
@@ -0,0 +1,285 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Globalization;
+using Microsoft.Extensions.Primitives;
+using Xunit;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class HeaderUtilitiesTest
+    {
+        private const string Rfc1123Format = "r";
+
+        [Theory]
+        [MemberData(nameof(TestValues))]
+        public void ReturnsSameResultAsRfc1123String(DateTimeOffset dateTime, bool quoted)
+        {
+            var formatted = dateTime.ToString(Rfc1123Format);
+            var expected = quoted ? $"\"{formatted}\"" : formatted;
+            var actual = HeaderUtilities.FormatDate(dateTime, quoted);
+
+            Assert.Equal(expected, actual);
+        }
+
+        public static TheoryData<DateTimeOffset, bool> TestValues
+        {
+            get
+            {
+                var data = new TheoryData<DateTimeOffset, bool>();
+
+                var date = new DateTimeOffset(new DateTime(2018, 1, 1, 1, 1, 1));
+
+                foreach (var quoted in new[] { true, false })
+                {
+                    data.Add(date, quoted);
+
+                    for (var i = 1; i < 60; i++)
+                    {
+                        data.Add(date.AddSeconds(i), quoted);
+                        data.Add(date.AddMinutes(i), quoted);
+                    }
+
+                    for (var i = 1; i < DateTime.DaysInMonth(date.Year, date.Month); i++)
+                    {
+                        data.Add(date.AddDays(i), quoted);
+                    }
+
+                    for (var i = 1; i < 11; i++)
+                    {
+                        data.Add(date.AddMonths(i), quoted);
+                    }
+
+                    for (var i = 1; i < 5; i++)
+                    {
+                        data.Add(date.AddYears(i), quoted);
+                    }
+                }
+
+                return data;
+            }
+        }
+
+        [Theory]
+        [InlineData("h=1", "h", 1)]
+        [InlineData("directive1=3, directive2=10", "directive1", 3)]
+        [InlineData("directive1   =45, directive2=80", "directive1", 45)]
+        [InlineData("directive1=   89   , directive2=22", "directive1", 89)]
+        [InlineData("directive1=   89   , directive2= 42", "directive2", 42)]
+        [InlineData("directive1=   89   , directive= 42", "directive", 42)]
+        [InlineData("directive1,,,,,directive2 = 42 ", "directive2", 42)]
+        [InlineData("directive1=;,directive2 = 42 ", "directive2", 42)]
+        [InlineData("directive1;;,;;,directive2 = 42 ", "directive2", 42)]
+        [InlineData("directive1=value;q=0.6,directive2 = 42 ", "directive2", 42)]
+        public void TryParseSeconds_Succeeds(string headerValues, string targetValue, int expectedValue)
+        {
+            TimeSpan? value;
+            Assert.True(HeaderUtilities.TryParseSeconds(new StringValues(headerValues), targetValue, out value));
+            Assert.Equal(TimeSpan.FromSeconds(expectedValue), value);
+        }
+
+        [Theory]
+        [InlineData("", "")]
+        [InlineData(null, null)]
+        [InlineData("h=", "h")]
+        [InlineData("directive1=, directive2=10", "directive1")]
+        [InlineData("directive1   , directive2=80", "directive1")]
+        [InlineData("h=10", "directive")]
+        [InlineData("directive1", "directive")]
+        [InlineData("directive1,,,,,,,", "directive")]
+        [InlineData("h=directive", "directive")]
+        [InlineData("directive1, directive2=80", "directive")]
+        [InlineData("directive1=;, directive2=10", "directive1")]
+        [InlineData("directive1;directive2=10", "directive2")]
+        public void TryParseSeconds_Fails(string headerValues, string targetValue)
+        {
+            TimeSpan? value;
+            Assert.False(HeaderUtilities.TryParseSeconds(new StringValues(headerValues), targetValue, out value));
+        }
+
+        [Theory]
+        [InlineData(0)]
+        [InlineData(1)]
+        [InlineData(1234567890)]
+        [InlineData(long.MaxValue)]
+        public void FormatNonNegativeInt64_MatchesToString(long value)
+        {
+            Assert.Equal(value.ToString(CultureInfo.InvariantCulture), HeaderUtilities.FormatNonNegativeInt64(value));
+        }
+
+        [Theory]
+        [InlineData(-1)]
+        [InlineData(-1234567890)]
+        [InlineData(long.MinValue)]
+        public void FormatNonNegativeInt64_Throws_ForNegativeValues(long value)
+        {
+            Assert.Throws<ArgumentOutOfRangeException>(() => HeaderUtilities.FormatNonNegativeInt64(value));
+        }
+
+        [Theory]
+        [InlineData("h", "h", true)]
+        [InlineData("h=", "h", true)]
+        [InlineData("h=1", "h", true)]
+        [InlineData("H", "h", true)]
+        [InlineData("H=", "h", true)]
+        [InlineData("H=1", "h", true)]
+        [InlineData("h", "H", true)]
+        [InlineData("h=", "H", true)]
+        [InlineData("h=1", "H", true)]
+        [InlineData("directive1, directive=10", "directive1", true)]
+        [InlineData("directive1=, directive=10", "directive1", true)]
+        [InlineData("directive1=3, directive=10", "directive1", true)]
+        [InlineData("directive1   , directive=80", "directive1", true)]
+        [InlineData("   directive1, directive=80", "directive1", true)]
+        [InlineData("directive1   =45, directive=80", "directive1", true)]
+        [InlineData("directive1=   89   , directive=22", "directive1", true)]
+        [InlineData("directive1, directive", "directive", true)]
+        [InlineData("directive1, directive=", "directive", true)]
+        [InlineData("directive1, directive=10", "directive", true)]
+        [InlineData("directive1=3, directive", "directive", true)]
+        [InlineData("directive1=3, directive=", "directive", true)]
+        [InlineData("directive1=3, directive=10", "directive", true)]
+        [InlineData("directive1=   89   , directive= 42", "directive", true)]
+        [InlineData("directive1=   89   , directive = 42", "directive", true)]
+        [InlineData("directive1,,,,,directive2 = 42 ", "directive2", true)]
+        [InlineData("directive1;;,;;,directive2 = 42 ", "directive2", true)]
+        [InlineData("directive1=;,directive2 = 42 ", "directive2", true)]
+        [InlineData("directive1=value;q=0.6,directive2 = 42 ", "directive2", true)]
+        [InlineData(null, null, false)]
+        [InlineData(null, "", false)]
+        [InlineData("", null, false)]
+        [InlineData("", "", false)]
+        [InlineData("h=10", "directive", false)]
+        [InlineData("directive1", "directive", false)]
+        [InlineData("directive1,,,,,,,", "directive", false)]
+        [InlineData("h=directive", "directive", false)]
+        [InlineData("directive1, directive2=80", "directive", false)]
+        [InlineData("directive1;, directive2=80", "directive", false)]
+        [InlineData("directive1=value;q=0.6;directive2 = 42 ", "directive2", false)]
+        public void ContainsCacheDirective_MatchesExactValue(string headerValues, string targetValue, bool contains)
+        {
+            Assert.Equal(contains, HeaderUtilities.ContainsCacheDirective(new StringValues(headerValues), targetValue));
+        }
+
+        [Theory]
+        [InlineData("")]
+        [InlineData(null)]
+        [InlineData("-1")]
+        [InlineData("a")]
+        [InlineData("1.1")]
+        [InlineData("9223372036854775808")] // long.MaxValue + 1
+        public void TryParseNonNegativeInt64_Fails(string valueString)
+        {
+            long value = 1;
+            Assert.False(HeaderUtilities.TryParseNonNegativeInt64(valueString, out value));
+            Assert.Equal(0, value);
+        }
+
+        [Theory]
+        [InlineData("0", 0)]
+        [InlineData("9223372036854775807", 9223372036854775807)] // long.MaxValue
+        public void TryParseNonNegativeInt64_Succeeds(string valueString, long expected)
+        {
+            long value = 1;
+            Assert.True(HeaderUtilities.TryParseNonNegativeInt64(valueString, out value));
+            Assert.Equal(expected, value);
+        }
+
+        [Theory]
+        [InlineData("")]
+        [InlineData(null)]
+        [InlineData("-1")]
+        [InlineData("a")]
+        [InlineData("1.1")]
+        [InlineData("1,000")]
+        [InlineData("2147483648")] // int.MaxValue + 1
+        public void TryParseNonNegativeInt32_Fails(string valueString)
+        {
+            int value = 1;
+            Assert.False(HeaderUtilities.TryParseNonNegativeInt32(valueString, out value));
+            Assert.Equal(0, value);
+        }
+
+        [Theory]
+        [InlineData("0", 0)]
+        [InlineData("2147483647", 2147483647)] // int.MaxValue
+        public void TryParseNonNegativeInt32_Succeeds(string valueString, long expected)
+        {
+            int value = 1;
+            Assert.True(HeaderUtilities.TryParseNonNegativeInt32(valueString, out value));
+            Assert.Equal(expected, value);
+        }
+
+        [Theory]
+        [InlineData("\"hello\"", "hello")]
+        [InlineData("\"hello", "\"hello")]
+        [InlineData("hello\"", "hello\"")]
+        [InlineData("\"\"hello\"\"", "\"hello\"")]
+        public void RemoveQuotes_BehaviorCheck(string input, string expected)
+        {
+            var actual = HeaderUtilities.RemoveQuotes(input);
+
+            Assert.Equal(expected, actual);
+        }
+        [Theory]
+        [InlineData("\"hello\"", true)]
+        [InlineData("\"hello", false)]
+        [InlineData("hello\"", false)]
+        [InlineData("\"\"hello\"\"", true)]
+        public void IsQuoted_BehaviorCheck(string input, bool expected)
+        {
+            var actual = HeaderUtilities.IsQuoted(input);
+
+            Assert.Equal(expected, actual);
+        }
+
+        [Theory]
+        [InlineData("value", "value")]
+        [InlineData("\"value\"", "value")]
+        [InlineData("\"hello\\\\\"", "hello\\")]
+        [InlineData("\"hello\\\"\"", "hello\"")]
+        [InlineData("\"hello\\\"foo\\\\bar\\\\baz\\\\\"", "hello\"foo\\bar\\baz\\")]
+        [InlineData("\"quoted value\"", "quoted value")]
+        [InlineData("\"quoted\\\"valuewithquote\"", "quoted\"valuewithquote")]
+        [InlineData("\"hello\\\"", "hello\\")]
+        public void UnescapeAsQuotedString_BehaviorCheck(string input, string expected)
+        {
+            var actual = HeaderUtilities.UnescapeAsQuotedString(input);
+
+            Assert.Equal(expected, actual);
+        }
+
+        [Theory]
+        [InlineData("value", "\"value\"")]
+        [InlineData("23", "\"23\"")]
+        [InlineData(";;;", "\";;;\"")]
+        [InlineData("\"value\"", "\"\\\"value\\\"\"")]
+        [InlineData("unquoted \"value", "\"unquoted \\\"value\"")]
+        [InlineData("value\\morevalues\\evenmorevalues", "\"value\\\\morevalues\\\\evenmorevalues\"")]
+        // We have to assume that the input needs to be quoted here
+        [InlineData("\"\"double quoted string\"\"", "\"\\\"\\\"double quoted string\\\"\\\"\"")]
+        [InlineData("\t", "\"\t\"")]
+        public void SetAndEscapeValue_BehaviorCheck(string input, string expected)
+        {
+            var actual = HeaderUtilities.EscapeAsQuotedString(input);
+
+            Assert.Equal(expected, actual);
+        }
+
+        [Theory]
+        [InlineData("\n")]
+        [InlineData("\b")]
+        [InlineData("\r")]
+        public void SetAndEscapeValue_ControlCharactersThrowFormatException(string input)
+        {
+            Assert.Throws<FormatException>(() => { var actual = HeaderUtilities.EscapeAsQuotedString(input); });
+        }
+
+        [Fact]
+        public void SetAndEscapeValue_ThrowsFormatExceptionOnDelCharacter()
+        {
+            Assert.Throws<FormatException>(() => { var actual = HeaderUtilities.EscapeAsQuotedString($"{(char)0x7F}"); });
+        }
+    }
+}
diff --git a/src/Http/Headers/test/MediaTypeHeaderValueComparerTests.cs b/src/Http/Headers/test/MediaTypeHeaderValueComparerTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3ce2702ec676f96f2be45abe925105254fbdf84c
--- /dev/null
+++ b/src/Http/Headers/test/MediaTypeHeaderValueComparerTests.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.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class MediaTypeHeaderValueComparerTests
+    {
+        public static IEnumerable<object[]> SortValues
+        {
+            get
+            {
+                yield return new object[] {
+                    new string[]
+                        {
+                            "application/*",
+                            "text/plain",
+                            "text/*+json;q=0.8",
+                            "text/plain;q=1.0",
+                            "text/plain",
+                            "text/*+json;q=0.6",
+                            "text/plain;q=0",
+                            "*/*;q=0.8",
+                            "*/*;q=1",
+                            "text/*;q=1",
+                            "text/plain;q=0.8",
+                            "text/*;q=0.8",
+                            "text/*;q=0.6",
+                            "text/*+json;q=0.4",
+                            "text/*;q=1.0",
+                            "*/*;q=0.4",
+                            "text/plain;q=0.6",
+                            "text/xml",
+                        },
+                    new string[]
+                        {
+                            "text/plain",
+                            "text/plain;q=1.0",
+                            "text/plain",
+                            "text/xml",
+                            "application/*",
+                            "text/*;q=1",
+                            "text/*;q=1.0",
+                            "*/*;q=1",
+                            "text/plain;q=0.8",
+                            "text/*+json;q=0.8",
+                            "text/*;q=0.8",
+                            "*/*;q=0.8",
+                            "text/plain;q=0.6",
+                            "text/*+json;q=0.6",
+                            "text/*;q=0.6",
+                            "text/*+json;q=0.4",
+                            "*/*;q=0.4",
+                            "text/plain;q=0",
+                        }
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(SortValues))]
+        public void SortMediaTypeHeaderValuesByQFactor_SortsCorrectly(IEnumerable<string> unsorted, IEnumerable<string> expectedSorted)
+        {
+            var unsortedValues = MediaTypeHeaderValue.ParseList(unsorted.ToList());
+            var expectedSortedValues = MediaTypeHeaderValue.ParseList(expectedSorted.ToList());
+
+            var actualSorted = unsortedValues.OrderByDescending(m => m, MediaTypeHeaderValueComparer.QualityComparer).ToList();
+
+            Assert.Equal(expectedSortedValues, actualSorted);
+        }
+    }
+}
diff --git a/src/Http/Headers/test/MediaTypeHeaderValueTest.cs b/src/Http/Headers/test/MediaTypeHeaderValueTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..75cccabc9c1d40ee8c22b93378b9e4590bbf5fbc
--- /dev/null
+++ b/src/Http/Headers/test/MediaTypeHeaderValueTest.cs
@@ -0,0 +1,847 @@
+// 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 Microsoft.Extensions.Primitives;
+using Xunit;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class MediaTypeHeaderValueTest
+    {
+        [Fact]
+        public void Ctor_MediaTypeNull_Throw()
+        {
+            Assert.Throws<ArgumentException>(() => new MediaTypeHeaderValue(null));
+            // null and empty should be treated the same. So we also throw for empty strings.
+            Assert.Throws<ArgumentException>(() => new MediaTypeHeaderValue(string.Empty));
+        }
+
+        [Fact]
+        public void Ctor_MediaTypeInvalidFormat_ThrowFormatException()
+        {
+            // When adding values using strongly typed objects, no leading/trailing LWS (whitespaces) are allowed.
+            AssertFormatException(" text/plain ");
+            AssertFormatException("text / plain");
+            AssertFormatException("text/ plain");
+            AssertFormatException("text /plain");
+            AssertFormatException("text/plain ");
+            AssertFormatException(" text/plain");
+            AssertFormatException("te xt/plain");
+            AssertFormatException("te=xt/plain");
+            AssertFormatException("teäxt/plain");
+            AssertFormatException("text/pläin");
+            AssertFormatException("text");
+            AssertFormatException("\"text/plain\"");
+            AssertFormatException("text/plain; charset=utf-8; ");
+            AssertFormatException("text/plain;");
+            AssertFormatException("text/plain;charset=utf-8"); // ctor takes only media-type name, no parameters
+        }
+
+        public static TheoryData<string, string, string> MediaTypesWithSuffixes =>
+                 new TheoryData<string, string, string>
+                 {
+                     // See https://tools.ietf.org/html/rfc6838#section-4.2 for allowed names spec
+                     { "application/json", "json", null },
+                     { "application/json+", "json", "" },
+                     { "application/+json", "", "json" },
+                     { "application/entitytype+json", "entitytype", "json" },
+                     { "applica+tion/entitytype+json", "entitytype", "json" },
+                 };
+
+        [Theory]
+        [MemberData(nameof(MediaTypesWithSuffixes))]
+        public void Ctor_CanParseSuffixedMediaTypes(string mediaType, string expectedSubTypeWithoutSuffix, string expectedSubTypeSuffix)
+        {
+            var result = new MediaTypeHeaderValue(mediaType);
+
+            Assert.Equal(new StringSegment(expectedSubTypeWithoutSuffix), result.SubTypeWithoutSuffix); // TODO consider overloading to have SubTypeWithoutSuffix?
+            Assert.Equal(new StringSegment(expectedSubTypeSuffix), result.Suffix);
+        }
+
+        public static TheoryData<string, string, string> MediaTypesWithSuffixesAndSpaces =>
+                 new TheoryData<string, string, string>
+                 {
+                     // See https://tools.ietf.org/html/rfc6838#section-4.2 for allowed names spec
+                     { "    application   /  json+xml", "json", "xml" },
+                     { "  application /  vnd.com-pany.some+entity!.v2+js.#$&^_n  ; q=\"0.3+1\"", "vnd.com-pany.some+entity!.v2", "js.#$&^_n"},
+                     { "   application/    +json", "", "json" },
+                     { "  application/   entitytype+json    ", "entitytype", "json" },
+                     { "  applica+tion/   entitytype+json    ", "entitytype", "json" }
+                 };
+
+        [Theory]
+        [MemberData(nameof(MediaTypesWithSuffixesAndSpaces))]
+        public void Parse_CanParseSuffixedMediaTypes(string mediaType, string expectedSubTypeWithoutSuffix, string expectedSubTypeSuffix)
+        {
+            var result = MediaTypeHeaderValue.Parse(mediaType);
+
+            Assert.Equal(new StringSegment(expectedSubTypeWithoutSuffix), result.SubTypeWithoutSuffix); // TODO consider overloading to have SubTypeWithoutSuffix?
+            Assert.Equal(new StringSegment(expectedSubTypeSuffix), result.Suffix);
+        }
+
+        [Theory]
+        [InlineData("*/*", true)]
+        [InlineData("text/*", true)]
+        [InlineData("text/*+suffix", true)]
+        [InlineData("text/*+", true)]
+        [InlineData("text/*+*", true)]
+        [InlineData("text/json+suffix", false)]
+        [InlineData("*/json+*", false)]
+        public void MatchesAllSubTypesWithoutSuffix_ReturnsExpectedResult(string value, bool expectedReturnValue)
+        {
+            // Arrange
+            var mediaType = new MediaTypeHeaderValue(value);
+
+            // Act
+            var result = mediaType.MatchesAllSubTypesWithoutSuffix;
+
+            // Assert
+            Assert.Equal(expectedReturnValue, result);
+        }
+
+        [Fact]
+        public void Ctor_MediaTypeValidFormat_SuccessfullyCreated()
+        {
+            var mediaType = new MediaTypeHeaderValue("text/plain");
+            Assert.Equal("text/plain", mediaType.MediaType);
+            Assert.Equal(0, mediaType.Parameters.Count);
+            Assert.Null(mediaType.Charset.Value);
+        }
+
+        [Fact]
+        public void Ctor_AddNameAndQuality_QualityParameterAdded()
+        {
+            var mediaType = new MediaTypeHeaderValue("application/xml", 0.08);
+            Assert.Equal(0.08, mediaType.Quality);
+            Assert.Equal("application/xml", mediaType.MediaType);
+            Assert.Equal(1, mediaType.Parameters.Count);
+        }
+
+        [Fact]
+        public void Parameters_AddNull_Throw()
+        {
+            var mediaType = new MediaTypeHeaderValue("text/plain");
+            Assert.Throws<ArgumentNullException>(() => mediaType.Parameters.Add(null));
+        }
+
+        [Fact]
+        public void Copy_SimpleMediaType_Copied()
+        {
+            var mediaType0 = new MediaTypeHeaderValue("text/plain");
+            var mediaType1 = mediaType0.Copy();
+            Assert.NotSame(mediaType0, mediaType1);
+            Assert.Same(mediaType0.MediaType.Value, mediaType1.MediaType.Value);
+            Assert.NotSame(mediaType0.Parameters, mediaType1.Parameters);
+            Assert.Equal(mediaType0.Parameters.Count, mediaType1.Parameters.Count);
+        }
+
+        [Fact]
+        public void CopyAsReadOnly_SimpleMediaType_CopiedAndReadOnly()
+        {
+            var mediaType0 = new MediaTypeHeaderValue("text/plain");
+            var mediaType1 = mediaType0.CopyAsReadOnly();
+            Assert.NotSame(mediaType0, mediaType1);
+            Assert.Same(mediaType0.MediaType.Value, mediaType1.MediaType.Value);
+            Assert.NotSame(mediaType0.Parameters, mediaType1.Parameters);
+            Assert.Equal(mediaType0.Parameters.Count, mediaType1.Parameters.Count);
+
+            Assert.False(mediaType0.IsReadOnly);
+            Assert.True(mediaType1.IsReadOnly);
+            Assert.Throws<InvalidOperationException>(() => { mediaType1.MediaType = "some/value"; });
+        }
+
+        [Fact]
+        public void Copy_WithParameters_Copied()
+        {
+            var mediaType0 = new MediaTypeHeaderValue("text/plain");
+            mediaType0.Parameters.Add(new NameValueHeaderValue("name", "value"));
+            var mediaType1 = mediaType0.Copy();
+            Assert.NotSame(mediaType0, mediaType1);
+            Assert.Same(mediaType0.MediaType.Value, mediaType1.MediaType.Value);
+            Assert.NotSame(mediaType0.Parameters, mediaType1.Parameters);
+            Assert.Equal(mediaType0.Parameters.Count, mediaType1.Parameters.Count);
+            var pair0 = mediaType0.Parameters.First();
+            var pair1 = mediaType1.Parameters.First();
+            Assert.NotSame(pair0, pair1);
+            Assert.Same(pair0.Name.Value, pair1.Name.Value);
+            Assert.Same(pair0.Value.Value, pair1.Value.Value);
+        }
+
+        [Fact]
+        public void CopyAsReadOnly_WithParameters_CopiedAndReadOnly()
+        {
+            var mediaType0 = new MediaTypeHeaderValue("text/plain");
+            mediaType0.Parameters.Add(new NameValueHeaderValue("name", "value"));
+            var mediaType1 = mediaType0.CopyAsReadOnly();
+            Assert.NotSame(mediaType0, mediaType1);
+            Assert.False(mediaType0.IsReadOnly);
+            Assert.True(mediaType1.IsReadOnly);
+            Assert.Same(mediaType0.MediaType.Value, mediaType1.MediaType.Value);
+
+            Assert.NotSame(mediaType0.Parameters, mediaType1.Parameters);
+            Assert.False(mediaType0.Parameters.IsReadOnly);
+            Assert.True(mediaType1.Parameters.IsReadOnly);
+            Assert.Equal(mediaType0.Parameters.Count, mediaType1.Parameters.Count);
+            Assert.Throws<NotSupportedException>(() => mediaType1.Parameters.Add(new NameValueHeaderValue("name")));
+            Assert.Throws<NotSupportedException>(() => mediaType1.Parameters.Remove(new NameValueHeaderValue("name")));
+            Assert.Throws<NotSupportedException>(() => mediaType1.Parameters.Clear());
+
+            var pair0 = mediaType0.Parameters.First();
+            var pair1 = mediaType1.Parameters.First();
+            Assert.NotSame(pair0, pair1);
+            Assert.False(pair0.IsReadOnly);
+            Assert.True(pair1.IsReadOnly);
+            Assert.Same(pair0.Name.Value, pair1.Name.Value);
+            Assert.Same(pair0.Value.Value, pair1.Value.Value);
+        }
+
+        [Fact]
+        public void CopyFromReadOnly_WithParameters_CopiedAsNonReadOnly()
+        {
+            var mediaType0 = new MediaTypeHeaderValue("text/plain");
+            mediaType0.Parameters.Add(new NameValueHeaderValue("name", "value"));
+            var mediaType1 = mediaType0.CopyAsReadOnly();
+            var mediaType2 = mediaType1.Copy();
+
+            Assert.NotSame(mediaType2, mediaType1);
+            Assert.Same(mediaType2.MediaType.Value, mediaType1.MediaType.Value);
+            Assert.True(mediaType1.IsReadOnly);
+            Assert.False(mediaType2.IsReadOnly);
+            Assert.NotSame(mediaType2.Parameters, mediaType1.Parameters);
+            Assert.Equal(mediaType2.Parameters.Count, mediaType1.Parameters.Count);
+            var pair2 = mediaType2.Parameters.First();
+            var pair1 = mediaType1.Parameters.First();
+            Assert.NotSame(pair2, pair1);
+            Assert.True(pair1.IsReadOnly);
+            Assert.False(pair2.IsReadOnly);
+            Assert.Same(pair2.Name.Value, pair1.Name.Value);
+            Assert.Same(pair2.Value.Value, pair1.Value.Value);
+        }
+
+        [Fact]
+        public void MediaType_SetAndGetMediaType_MatchExpectations()
+        {
+            var mediaType = new MediaTypeHeaderValue("text/plain");
+            Assert.Equal("text/plain", mediaType.MediaType);
+
+            mediaType.MediaType = "application/xml";
+            Assert.Equal("application/xml", mediaType.MediaType);
+        }
+
+        [Fact]
+        public void Charset_SetCharsetAndValidateObject_ParametersEntryForCharsetAdded()
+        {
+            var mediaType = new MediaTypeHeaderValue("text/plain");
+            mediaType.Charset = "mycharset";
+            Assert.Equal("mycharset", mediaType.Charset);
+            Assert.Equal(1, mediaType.Parameters.Count);
+            Assert.Equal("charset", mediaType.Parameters.First().Name);
+
+            mediaType.Charset = null;
+            Assert.Null(mediaType.Charset.Value);
+            Assert.Equal(0, mediaType.Parameters.Count);
+            mediaType.Charset = null; // It's OK to set it again to null; no exception.
+        }
+
+        [Fact]
+        public void Charset_AddCharsetParameterThenUseProperty_ParametersEntryIsOverwritten()
+        {
+            var mediaType = new MediaTypeHeaderValue("text/plain");
+
+            // Note that uppercase letters are used. Comparison should happen case-insensitive.
+            var charset = new NameValueHeaderValue("CHARSET", "old_charset");
+            mediaType.Parameters.Add(charset);
+            Assert.Equal(1, mediaType.Parameters.Count);
+            Assert.Equal("CHARSET", mediaType.Parameters.First().Name);
+
+            mediaType.Charset = "new_charset";
+            Assert.Equal("new_charset", mediaType.Charset);
+            Assert.Equal(1, mediaType.Parameters.Count);
+            Assert.Equal("CHARSET", mediaType.Parameters.First().Name);
+
+            mediaType.Parameters.Remove(charset);
+            Assert.Null(mediaType.Charset.Value);
+        }
+
+        [Fact]
+        public void Quality_SetCharsetAndValidateObject_ParametersEntryForCharsetAdded()
+        {
+            var mediaType = new MediaTypeHeaderValue("text/plain");
+            mediaType.Quality = 0.563156454;
+            Assert.Equal(0.563, mediaType.Quality);
+            Assert.Equal(1, mediaType.Parameters.Count);
+            Assert.Equal("q", mediaType.Parameters.First().Name);
+            Assert.Equal("0.563", mediaType.Parameters.First().Value);
+
+            mediaType.Quality = null;
+            Assert.Null(mediaType.Quality);
+            Assert.Equal(0, mediaType.Parameters.Count);
+            mediaType.Quality = null; // It's OK to set it again to null; no exception.
+        }
+
+        [Fact]
+        public void Quality_AddQualityParameterThenUseProperty_ParametersEntryIsOverwritten()
+        {
+            var mediaType = new MediaTypeHeaderValue("text/plain");
+
+            var quality = new NameValueHeaderValue("q", "0.132");
+            mediaType.Parameters.Add(quality);
+            Assert.Equal(1, mediaType.Parameters.Count);
+            Assert.Equal("q", mediaType.Parameters.First().Name);
+            Assert.Equal(0.132, mediaType.Quality);
+
+            mediaType.Quality = 0.9;
+            Assert.Equal(0.9, mediaType.Quality);
+            Assert.Equal(1, mediaType.Parameters.Count);
+            Assert.Equal("q", mediaType.Parameters.First().Name);
+
+            mediaType.Parameters.Remove(quality);
+            Assert.Null(mediaType.Quality);
+        }
+
+        [Fact]
+        public void Quality_AddQualityParameterUpperCase_CaseInsensitiveComparison()
+        {
+            var mediaType = new MediaTypeHeaderValue("text/plain");
+
+            var quality = new NameValueHeaderValue("Q", "0.132");
+            mediaType.Parameters.Add(quality);
+            Assert.Equal(1, mediaType.Parameters.Count);
+            Assert.Equal("Q", mediaType.Parameters.First().Name);
+            Assert.Equal(0.132, mediaType.Quality);
+        }
+
+        [Fact]
+        public void Quality_LessThanZero_Throw()
+        {
+            Assert.Throws<ArgumentOutOfRangeException>(() => new MediaTypeHeaderValue("application/xml", -0.01));
+        }
+
+        [Fact]
+        public void Quality_GreaterThanOne_Throw()
+        {
+            var mediaType = new MediaTypeHeaderValue("application/xml");
+            Assert.Throws<ArgumentOutOfRangeException>(() => mediaType.Quality = 1.01);
+        }
+
+        [Fact]
+        public void ToString_UseDifferentMediaTypes_AllSerializedCorrectly()
+        {
+            var mediaType = new MediaTypeHeaderValue("text/plain");
+            Assert.Equal("text/plain", mediaType.ToString());
+
+            mediaType.Charset = "utf-8";
+            Assert.Equal("text/plain; charset=utf-8", mediaType.ToString());
+
+            mediaType.Parameters.Add(new NameValueHeaderValue("custom", "\"custom value\""));
+            Assert.Equal("text/plain; charset=utf-8; custom=\"custom value\"", mediaType.ToString());
+
+            mediaType.Charset = null;
+            Assert.Equal("text/plain; custom=\"custom value\"", mediaType.ToString());
+        }
+
+        [Fact]
+        public void GetHashCode_UseMediaTypeWithAndWithoutParameters_SameOrDifferentHashCodes()
+        {
+            var mediaType1 = new MediaTypeHeaderValue("text/plain");
+            var mediaType2 = new MediaTypeHeaderValue("text/plain");
+            mediaType2.Charset = "utf-8";
+            var mediaType3 = new MediaTypeHeaderValue("text/plain");
+            mediaType3.Parameters.Add(new NameValueHeaderValue("name", "value"));
+            var mediaType4 = new MediaTypeHeaderValue("TEXT/plain");
+            var mediaType5 = new MediaTypeHeaderValue("TEXT/plain");
+            mediaType5.Parameters.Add(new NameValueHeaderValue("CHARSET", "UTF-8"));
+
+            Assert.NotEqual(mediaType1.GetHashCode(), mediaType2.GetHashCode());
+            Assert.NotEqual(mediaType1.GetHashCode(), mediaType3.GetHashCode());
+            Assert.NotEqual(mediaType2.GetHashCode(), mediaType3.GetHashCode());
+            Assert.Equal(mediaType1.GetHashCode(), mediaType4.GetHashCode());
+            Assert.Equal(mediaType2.GetHashCode(), mediaType5.GetHashCode());
+        }
+
+        [Fact]
+        public void Equals_UseMediaTypeWithAndWithoutParameters_EqualOrNotEqualNoExceptions()
+        {
+            var mediaType1 = new MediaTypeHeaderValue("text/plain");
+            var mediaType2 = new MediaTypeHeaderValue("text/plain");
+            mediaType2.Charset = "utf-8";
+            var mediaType3 = new MediaTypeHeaderValue("text/plain");
+            mediaType3.Parameters.Add(new NameValueHeaderValue("name", "value"));
+            var mediaType4 = new MediaTypeHeaderValue("TEXT/plain");
+            var mediaType5 = new MediaTypeHeaderValue("TEXT/plain");
+            mediaType5.Parameters.Add(new NameValueHeaderValue("CHARSET", "UTF-8"));
+            var mediaType6 = new MediaTypeHeaderValue("TEXT/plain");
+            mediaType6.Parameters.Add(new NameValueHeaderValue("CHARSET", "UTF-8"));
+            mediaType6.Parameters.Add(new NameValueHeaderValue("custom", "value"));
+            var mediaType7 = new MediaTypeHeaderValue("text/other");
+
+            Assert.False(mediaType1.Equals(mediaType2), "No params vs. charset.");
+            Assert.False(mediaType2.Equals(mediaType1), "charset vs. no params.");
+            Assert.False(mediaType1.Equals(null), "No params vs. <null>.");
+            Assert.False(mediaType1.Equals(mediaType3), "No params vs. custom param.");
+            Assert.False(mediaType2.Equals(mediaType3), "charset vs. custom param.");
+            Assert.True(mediaType1.Equals(mediaType4), "Different casing.");
+            Assert.True(mediaType2.Equals(mediaType5), "Different casing in charset.");
+            Assert.False(mediaType5.Equals(mediaType6), "charset vs. custom param.");
+            Assert.False(mediaType1.Equals(mediaType7), "text/plain vs. text/other.");
+        }
+
+        [Fact]
+        public void Parse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            CheckValidParse("\r\n text/plain  ", new MediaTypeHeaderValue("text/plain"));
+            CheckValidParse("text/plain", new MediaTypeHeaderValue("text/plain"));
+
+            CheckValidParse("\r\n text   /  plain ;  charset =   utf-8 ", new MediaTypeHeaderValue("text/plain") { Charset = "utf-8" });
+            CheckValidParse("  text/plain;charset=utf-8", new MediaTypeHeaderValue("text/plain") { Charset = "utf-8" });
+
+            CheckValidParse("text/plain; charset=iso-8859-1", new MediaTypeHeaderValue("text/plain") { Charset = "iso-8859-1" });
+
+            var expected = new MediaTypeHeaderValue("text/plain") { Charset = "utf-8" };
+            expected.Parameters.Add(new NameValueHeaderValue("custom", "value"));
+            CheckValidParse(" text/plain; custom=value;charset=utf-8", expected);
+
+            expected = new MediaTypeHeaderValue("text/plain");
+            expected.Parameters.Add(new NameValueHeaderValue("custom"));
+            CheckValidParse(" text/plain; custom", expected);
+
+            expected = new MediaTypeHeaderValue("text/plain") { Charset = "utf-8" };
+            expected.Parameters.Add(new NameValueHeaderValue("custom", "\"x\""));
+            CheckValidParse("text / plain ; custom =\r\n \"x\" ; charset = utf-8 ", expected);
+
+            expected = new MediaTypeHeaderValue("text/plain") { Charset = "utf-8" };
+            expected.Parameters.Add(new NameValueHeaderValue("custom", "\"x\""));
+            CheckValidParse("text/plain;custom=\"x\";charset=utf-8", expected);
+
+            expected = new MediaTypeHeaderValue("text/plain");
+            CheckValidParse("text/plain;", expected);
+
+            expected = new MediaTypeHeaderValue("text/plain");
+            expected.Parameters.Add(new NameValueHeaderValue("name", ""));
+            CheckValidParse("text/plain;name=", expected);
+
+            expected = new MediaTypeHeaderValue("text/plain");
+            expected.Parameters.Add(new NameValueHeaderValue("name", "value"));
+            CheckValidParse("text/plain;name=value;", expected);
+
+            expected = new MediaTypeHeaderValue("text/plain");
+            expected.Charset = "iso-8859-1";
+            expected.Quality = 1.0;
+            CheckValidParse("text/plain; charset=iso-8859-1; q=1.0", expected);
+
+            expected = new MediaTypeHeaderValue("*/xml");
+            expected.Charset = "utf-8";
+            expected.Quality = 0.5;
+            CheckValidParse("\r\n */xml; charset=utf-8; q=0.5", expected);
+
+            expected = new MediaTypeHeaderValue("*/*");
+            CheckValidParse("*/*", expected);
+
+            expected = new MediaTypeHeaderValue("text/*");
+            expected.Charset = "utf-8";
+            expected.Parameters.Add(new NameValueHeaderValue("foo", "bar"));
+            CheckValidParse("text/*; charset=utf-8; foo=bar", expected);
+
+            expected = new MediaTypeHeaderValue("text/plain");
+            expected.Charset = "utf-8";
+            expected.Quality = 0;
+            expected.Parameters.Add(new NameValueHeaderValue("foo", "bar"));
+            CheckValidParse("text/plain; charset=utf-8; foo=bar; q=0.0", expected);
+        }
+
+        [Fact]
+        public void Parse_SetOfInvalidValueStrings_Throws()
+        {
+            CheckInvalidParse("");
+            CheckInvalidParse("  ");
+            CheckInvalidParse(null);
+            CheckInvalidParse("text/plain会");
+            CheckInvalidParse("text/plain ,");
+            CheckInvalidParse("text/plain,");
+            CheckInvalidParse("text/plain; charset=utf-8 ,");
+            CheckInvalidParse("text/plain; charset=utf-8,");
+            CheckInvalidParse("textplain");
+            CheckInvalidParse("text/");
+            CheckInvalidParse(",, , ,,text/plain; charset=iso-8859-1; q=1.0,\r\n */xml; charset=utf-8; q=0.5,,,");
+            CheckInvalidParse("text/plain; charset=iso-8859-1; q=1.0, */xml; charset=utf-8; q=0.5");
+            CheckInvalidParse(" , */xml; charset=utf-8; q=0.5 ");
+            CheckInvalidParse("text/plain; charset=iso-8859-1; q=1.0 , ");
+        }
+
+        [Fact]
+        public void TryParse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            var expected = new MediaTypeHeaderValue("text/plain");
+            CheckValidTryParse("\r\n text/plain  ", expected);
+            CheckValidTryParse("text/plain", expected);
+
+            // We don't have to test all possible input strings, since most of the pieces are handled by other parsers.
+            // The purpose of this test is to verify that these other parsers are combined correctly to build a
+            // media-type parser.
+            expected.Charset = "utf-8";
+            CheckValidTryParse("\r\n text   /  plain ;  charset =   utf-8 ", expected);
+            CheckValidTryParse("  text/plain;charset=utf-8", expected);
+
+            var value1 = new MediaTypeHeaderValue("text/plain");
+            value1.Charset = "iso-8859-1";
+            value1.Quality = 1.0;
+
+            CheckValidTryParse("text/plain; charset=iso-8859-1; q=1.0", value1);
+
+            var value2 = new MediaTypeHeaderValue("*/xml");
+            value2.Charset = "utf-8";
+            value2.Quality = 0.5;
+
+            CheckValidTryParse("\r\n */xml; charset=utf-8; q=0.5", value2);
+        }
+
+        [Fact]
+        public void TryParse_SetOfInvalidValueStrings_ReturnsFalse()
+        {
+            CheckInvalidTryParse("");
+            CheckInvalidTryParse("  ");
+            CheckInvalidTryParse(null);
+            CheckInvalidTryParse("text/plain会");
+            CheckInvalidTryParse("text/plain ,");
+            CheckInvalidTryParse("text/plain,");
+            CheckInvalidTryParse("text/plain; charset=utf-8 ,");
+            CheckInvalidTryParse("text/plain; charset=utf-8,");
+            CheckInvalidTryParse("textplain");
+            CheckInvalidTryParse("text/");
+            CheckInvalidTryParse(",, , ,,text/plain; charset=iso-8859-1; q=1.0,\r\n */xml; charset=utf-8; q=0.5,,,");
+            CheckInvalidTryParse("text/plain; charset=iso-8859-1; q=1.0, */xml; charset=utf-8; q=0.5");
+            CheckInvalidTryParse(" , */xml; charset=utf-8; q=0.5 ");
+            CheckInvalidTryParse("text/plain; charset=iso-8859-1; q=1.0 , ");
+        }
+
+        [Fact]
+        public void ParseList_NullOrEmptyArray_ReturnsEmptyList()
+        {
+            var results = MediaTypeHeaderValue.ParseList(null);
+            Assert.NotNull(results);
+            Assert.Equal(0, results.Count);
+
+            results = MediaTypeHeaderValue.ParseList(new string[0]);
+            Assert.NotNull(results);
+            Assert.Equal(0, results.Count);
+
+            results = MediaTypeHeaderValue.ParseList(new string[] { "" });
+            Assert.NotNull(results);
+            Assert.Equal(0, results.Count);
+        }
+
+        [Fact]
+        public void TryParseList_NullOrEmptyArray_ReturnsFalse()
+        {
+            IList<MediaTypeHeaderValue> results;
+            Assert.False(MediaTypeHeaderValue.TryParseList(null, out results));
+            Assert.False(MediaTypeHeaderValue.TryParseList(new string[0], out results));
+            Assert.False(MediaTypeHeaderValue.TryParseList(new string[] { "" }, out results));
+        }
+
+        [Fact]
+        public void ParseList_SetOfValidValueStrings_ReturnsValues()
+        {
+            var inputs = new[] { "text/html,application/xhtml+xml,", "application/xml;q=0.9,image/webp,*/*;q=0.8" };
+            var results = MediaTypeHeaderValue.ParseList(inputs);
+
+            var expectedResults = new[]
+            {
+                new MediaTypeHeaderValue("text/html"),
+                new MediaTypeHeaderValue("application/xhtml+xml"),
+                new MediaTypeHeaderValue("application/xml", 0.9),
+                new MediaTypeHeaderValue("image/webp"),
+                new MediaTypeHeaderValue("*/*", 0.8),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void ParseStrictList_SetOfValidValueStrings_ReturnsValues()
+        {
+            var inputs = new[] { "text/html,application/xhtml+xml,", "application/xml;q=0.9,image/webp,*/*;q=0.8" };
+            var results = MediaTypeHeaderValue.ParseStrictList(inputs);
+
+            var expectedResults = new[]
+            {
+                new MediaTypeHeaderValue("text/html"),
+                new MediaTypeHeaderValue("application/xhtml+xml"),
+                new MediaTypeHeaderValue("application/xml", 0.9),
+                new MediaTypeHeaderValue("image/webp"),
+                new MediaTypeHeaderValue("*/*", 0.8),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void TryParseList_SetOfValidValueStrings_ReturnsTrue()
+        {
+            var inputs = new[] { "text/html,application/xhtml+xml,", "application/xml;q=0.9,image/webp,*/*;q=0.8" };
+            IList<MediaTypeHeaderValue> results;
+            Assert.True(MediaTypeHeaderValue.TryParseList(inputs, out results));
+
+            var expectedResults = new[]
+            {
+                new MediaTypeHeaderValue("text/html"),
+                new MediaTypeHeaderValue("application/xhtml+xml"),
+                new MediaTypeHeaderValue("application/xml", 0.9),
+                new MediaTypeHeaderValue("image/webp"),
+                new MediaTypeHeaderValue("*/*", 0.8),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void TryParseStrictList_SetOfValidValueStrings_ReturnsTrue()
+        {
+            var inputs = new[] { "text/html,application/xhtml+xml,", "application/xml;q=0.9,image/webp,*/*;q=0.8" };
+            IList<MediaTypeHeaderValue> results;
+            Assert.True(MediaTypeHeaderValue.TryParseStrictList(inputs, out results));
+
+            var expectedResults = new[]
+            {
+                new MediaTypeHeaderValue("text/html"),
+                new MediaTypeHeaderValue("application/xhtml+xml"),
+                new MediaTypeHeaderValue("application/xml", 0.9),
+                new MediaTypeHeaderValue("image/webp"),
+                new MediaTypeHeaderValue("*/*", 0.8),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void ParseList_WithSomeInvlaidValues_IgnoresInvalidValues()
+        {
+            var inputs = new[]
+            {
+                "text/html,application/xhtml+xml, ignore-this, ignore/this",
+                "application/xml;q=0.9,image/webp,*/*;q=0.8"
+            };
+            var results = MediaTypeHeaderValue.ParseList(inputs);
+
+            var expectedResults = new[]
+            {
+                new MediaTypeHeaderValue("text/html"),
+                new MediaTypeHeaderValue("application/xhtml+xml"),
+                new MediaTypeHeaderValue("ignore/this"),
+                new MediaTypeHeaderValue("application/xml", 0.9),
+                new MediaTypeHeaderValue("image/webp"),
+                new MediaTypeHeaderValue("*/*", 0.8),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void ParseStrictList_WithSomeInvlaidValues_Throws()
+        {
+            var inputs = new[]
+            {
+                "text/html,application/xhtml+xml, ignore-this, ignore/this",
+                "application/xml;q=0.9,image/webp,*/*;q=0.8"
+            };
+            Assert.Throws<FormatException>(() => MediaTypeHeaderValue.ParseStrictList(inputs));
+        }
+
+        [Fact]
+        public void TryParseList_WithSomeInvlaidValues_IgnoresInvalidValues()
+        {
+            var inputs = new[]
+            {
+                "text/html,application/xhtml+xml, ignore-this, ignore/this",
+                "application/xml;q=0.9,image/webp,*/*;q=0.8",
+                "application/xml;q=0 4"
+            };
+            IList<MediaTypeHeaderValue> results;
+            Assert.True(MediaTypeHeaderValue.TryParseList(inputs, out results));
+
+            var expectedResults = new[]
+            {
+                new MediaTypeHeaderValue("text/html"),
+                new MediaTypeHeaderValue("application/xhtml+xml"),
+                new MediaTypeHeaderValue("ignore/this"),
+                new MediaTypeHeaderValue("application/xml", 0.9),
+                new MediaTypeHeaderValue("image/webp"),
+                new MediaTypeHeaderValue("*/*", 0.8),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void TryParseStrictList_WithSomeInvlaidValues_ReturnsFalse()
+        {
+            var inputs = new[]
+            {
+                "text/html,application/xhtml+xml, ignore-this, ignore/this",
+                "application/xml;q=0.9,image/webp,*/*;q=0.8",
+                "application/xml;q=0 4"
+            };
+            IList<MediaTypeHeaderValue> results;
+            Assert.False(MediaTypeHeaderValue.TryParseStrictList(inputs, out results));
+        }
+
+        [Theory]
+        [InlineData("*/*;", "*/*")]
+        [InlineData("text/*", "text/*")]
+        [InlineData("text/*;", "*/*")]
+        [InlineData("text/plain;", "text/plain")]
+        [InlineData("text/plain", "text/*")]
+        [InlineData("text/plain;", "*/*")]
+        [InlineData("*/*;missingparam=4", "*/*")]
+        [InlineData("text/*;missingparam=4;", "*/*;")]
+        [InlineData("text/plain;missingparam=4", "*/*;")]
+        [InlineData("text/plain;missingparam=4", "text/*")]
+        [InlineData("text/plain;charset=utf-8", "text/plain;charset=utf-8")]
+        [InlineData("text/plain;version=v1", "Text/plain;Version=v1")]
+        [InlineData("text/plain;version=v1", "tExT/plain;version=V1")]
+        [InlineData("text/plain;version=v1", "TEXT/PLAIN;VERSION=V1")]
+        [InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "text/plain;charset=utf-8;foo=bar;q=0.0")]
+        [InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "text/plain;foo=bar;q=0.0;charset=utf-8")] // different order of parameters
+        [InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "text/*;charset=utf-8;foo=bar;q=0.0")]
+        [InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "*/*;charset=utf-8;foo=bar;q=0.0")]
+        [InlineData("application/json;v=2", "application/json;*")]
+        [InlineData("application/json;v=2;charset=utf-8", "application/json;v=2;*")]
+        public void IsSubsetOf_PositiveCases(string mediaType1, string mediaType2)
+        {
+            // Arrange
+            var parsedMediaType1 = MediaTypeHeaderValue.Parse(mediaType1);
+            var parsedMediaType2 = MediaTypeHeaderValue.Parse(mediaType2);
+
+            // Act
+            var isSubset = parsedMediaType1.IsSubsetOf(parsedMediaType2);
+
+            // Assert
+            Assert.True(isSubset);
+        }
+
+        [Theory]
+        [InlineData("application/html", "text/*")]
+        [InlineData("application/json", "application/html")]
+        [InlineData("text/plain;version=v1", "text/plain;version=")]
+        [InlineData("*/*;", "text/plain;charset=utf-8;foo=bar;q=0.0")]
+        [InlineData("text/*;", "text/plain;charset=utf-8;foo=bar;q=0.0")]
+        [InlineData("text/*;charset=utf-8;foo=bar;q=0.0", "text/plain;missingparam=4;")]
+        [InlineData("*/*;charset=utf-8;foo=bar;q=0.0", "text/plain;missingparam=4;")]
+        [InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "text/plain;missingparam=4;")]
+        [InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "text/*;missingparam=4;")]
+        [InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "*/*;missingparam=4;")]
+        public void IsSubsetOf_NegativeCases(string mediaType1, string mediaType2)
+        {
+            // Arrange
+            var parsedMediaType1 = MediaTypeHeaderValue.Parse(mediaType1);
+            var parsedMediaType2 = MediaTypeHeaderValue.Parse(mediaType2);
+
+            // Act
+            var isSubset = parsedMediaType1.IsSubsetOf(parsedMediaType2);
+
+            // Assert
+            Assert.False(isSubset);
+        }
+
+        [Theory]
+        [InlineData("application/entity+json", "application/entity+json")]
+        [InlineData("application/*+json", "application/entity+json")]
+        [InlineData("application/*+json", "application/*+json")]
+        [InlineData("application/*", "application/*+JSON")]
+        [InlineData("application/vnd.github+json", "application/vnd.github+json")]
+        [InlineData("application/*", "application/entity+JSON")]
+        [InlineData("*/*", "application/entity+json")]
+        public void IsSubsetOfWithSuffixes_PositiveCases(string set, string subset)
+        {
+            // Arrange
+            var setMediaType = MediaTypeHeaderValue.Parse(set);
+            var subSetMediaType = MediaTypeHeaderValue.Parse(subset);
+
+            // Act
+            var result = subSetMediaType.IsSubsetOf(setMediaType);
+
+            // Assert
+            Assert.True(result);
+        }
+
+        [Theory]
+        [InlineData("application/entity+json", "application/entity+txt")]
+        [InlineData("application/entity+json", "application/entity.v2+json")]
+        [InlineData("application/*+json", "application/entity+txt")]
+        [InlineData("application/*+*", "application/json")]
+        [InlineData("application/entity+*", "application/entity+json")] // We don't allow suffixes to be wildcards
+        [InlineData("application/*+*", "application/entity+json")] // We don't allow suffixes to be wildcards
+        public void IsSubSetOfWithSuffixes_NegativeCases(string set, string subset)
+        {
+            // Arrange
+            var setMediaType = MediaTypeHeaderValue.Parse(set);
+            var subSetMediaType = MediaTypeHeaderValue.Parse(subset);
+
+            // Act
+            var result = subSetMediaType.IsSubsetOf(setMediaType);
+
+            // Assert
+            Assert.False(result);
+        }
+
+        public static TheoryData<string, List<StringSegment>> MediaTypesWithFacets =>
+                 new TheoryData<string, List<StringSegment>>
+                 {
+                     { "application/vdn.github",
+                         new List<StringSegment>(){ "vdn", "github" } },
+                     { "application/vdn.github+json",
+                         new List<StringSegment>(){ "vdn", "github" } },
+                     { "application/vdn.github.v3+json",
+                         new List<StringSegment>(){ "vdn", "github", "v3" } },
+                     { "application/vdn.github.+json",
+                         new List<StringSegment>(){ "vdn", "github", "" } },
+                 };
+
+        [Theory]
+        [MemberData(nameof(MediaTypesWithFacets))]
+        public void Facets_TestPositiveCases(string input, List<StringSegment> expected)
+        {
+            // Arrange
+            var mediaType = MediaTypeHeaderValue.Parse(input);
+
+            // Act
+            var result = mediaType.Facets;
+
+            // Assert
+            Assert.Equal(expected, result);
+        }
+
+        private void CheckValidParse(string input, MediaTypeHeaderValue expectedResult)
+        {
+            var result = MediaTypeHeaderValue.Parse(input);
+            Assert.Equal(expectedResult, result);
+        }
+
+        private void CheckInvalidParse(string input)
+        {
+            Assert.Throws<FormatException>(() => MediaTypeHeaderValue.Parse(input));
+        }
+
+        private void CheckValidTryParse(string input, MediaTypeHeaderValue expectedResult)
+        {
+            MediaTypeHeaderValue result = null;
+            Assert.True(MediaTypeHeaderValue.TryParse(input, out result));
+            Assert.Equal(expectedResult, result);
+        }
+
+        private void CheckInvalidTryParse(string input)
+        {
+            MediaTypeHeaderValue result = null;
+            Assert.False(MediaTypeHeaderValue.TryParse(input, out result));
+            Assert.Null(result);
+        }
+
+        private static void AssertFormatException(string mediaType)
+        {
+            Assert.Throws<FormatException>(() => new MediaTypeHeaderValue(mediaType));
+        }
+    }
+}
diff --git a/src/Http/Headers/test/Microsoft.Net.Http.Headers.Tests.csproj b/src/Http/Headers/test/Microsoft.Net.Http.Headers.Tests.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..eb53233e33be88e61e93b6f7a425f0182c332943
--- /dev/null
+++ b/src/Http/Headers/test/Microsoft.Net.Http.Headers.Tests.csproj
@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.Net.Http.Headers" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/Headers/test/NameValueHeaderValueTest.cs b/src/Http/Headers/test/NameValueHeaderValueTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..cac18debbbd2e0f6cf9505690e752ab09b8eb160
--- /dev/null
+++ b/src/Http/Headers/test/NameValueHeaderValueTest.cs
@@ -0,0 +1,699 @@
+// 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 Xunit;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class NameValueHeaderValueTest
+    {
+        [Fact]
+        public void Ctor_NameNull_Throw()
+        {
+            Assert.Throws<ArgumentException>(() => new NameValueHeaderValue(null));
+            // null and empty should be treated the same. So we also throw for empty strings.
+            Assert.Throws<ArgumentException>(() => new NameValueHeaderValue(string.Empty));
+        }
+
+        [Fact]
+        public void Ctor_NameInvalidFormat_ThrowFormatException()
+        {
+            // When adding values using strongly typed objects, no leading/trailing LWS (whitespaces) are allowed.
+            AssertFormatException(" text ", null);
+            AssertFormatException("text ", null);
+            AssertFormatException(" text", null);
+            AssertFormatException("te xt", null);
+            AssertFormatException("te=xt", null); // The ctor takes a name which must not contain '='.
+            AssertFormatException("teäxt", null);
+        }
+
+        [Fact]
+        public void Ctor_NameValidFormat_SuccessfullyCreated()
+        {
+            var nameValue = new NameValueHeaderValue("text", null);
+            Assert.Equal("text", nameValue.Name);
+        }
+
+        [Fact]
+        public void Ctor_ValueInvalidFormat_ThrowFormatException()
+        {
+            // When adding values using strongly typed objects, no leading/trailing LWS (whitespaces) are allowed.
+            AssertFormatException("text", " token ");
+            AssertFormatException("text", "token ");
+            AssertFormatException("text", " token");
+            AssertFormatException("text", "token string");
+            AssertFormatException("text", "\"quoted string with \" quotes\"");
+            AssertFormatException("text", "\"quoted string with \"two\" quotes\"");
+        }
+
+        [Fact]
+        public void Ctor_ValueValidFormat_SuccessfullyCreated()
+        {
+            CheckValue(null);
+            CheckValue(string.Empty);
+            CheckValue("token_string");
+            CheckValue("\"quoted string\"");
+            CheckValue("\"quoted string with quoted \\\" quote-pair\"");
+        }
+
+        [Fact]
+        public void Copy_NameOnly_SuccesfullyCopied()
+        {
+            var pair0 = new NameValueHeaderValue("name");
+            var pair1 = pair0.Copy();
+            Assert.NotSame(pair0, pair1);
+            Assert.Same(pair0.Name.Value, pair1.Name.Value);
+            Assert.Null(pair0.Value.Value);
+            Assert.Null(pair1.Value.Value);
+
+            // Change one value and verify the other is unchanged.
+            pair0.Value = "othervalue";
+            Assert.Equal("othervalue", pair0.Value);
+            Assert.Null(pair1.Value.Value);
+        }
+
+        [Fact]
+        public void CopyAsReadOnly_NameOnly_CopiedAndReadOnly()
+        {
+            var pair0 = new NameValueHeaderValue("name");
+            var pair1 = pair0.CopyAsReadOnly();
+            Assert.NotSame(pair0, pair1);
+            Assert.Same(pair0.Name.Value, pair1.Name.Value);
+            Assert.Null(pair0.Value.Value);
+            Assert.Null(pair1.Value.Value);
+            Assert.False(pair0.IsReadOnly);
+            Assert.True(pair1.IsReadOnly);
+
+            // Change one value and verify the other is unchanged.
+            pair0.Value = "othervalue";
+            Assert.Equal("othervalue", pair0.Value);
+            Assert.Null(pair1.Value.Value);
+            Assert.Throws<InvalidOperationException>(() => { pair1.Value = "othervalue"; });
+        }
+
+        [Fact]
+        public void Copy_NameAndValue_SuccesfullyCopied()
+        {
+            var pair0 = new NameValueHeaderValue("name", "value");
+            var pair1 = pair0.Copy();
+            Assert.NotSame(pair0, pair1);
+            Assert.Same(pair0.Name.Value, pair1.Name.Value);
+            Assert.Same(pair0.Value.Value, pair1.Value.Value);
+
+            // Change one value and verify the other is unchanged.
+            pair0.Value = "othervalue";
+            Assert.Equal("othervalue", pair0.Value);
+            Assert.Equal("value", pair1.Value);
+        }
+
+        [Fact]
+        public void CopyAsReadOnly_NameAndValue_CopiedAndReadOnly()
+        {
+            var pair0 = new NameValueHeaderValue("name", "value");
+            var pair1 = pair0.CopyAsReadOnly();
+            Assert.NotSame(pair0, pair1);
+            Assert.Same(pair0.Name.Value, pair1.Name.Value);
+            Assert.Same(pair0.Value.Value, pair1.Value.Value);
+            Assert.False(pair0.IsReadOnly);
+            Assert.True(pair1.IsReadOnly);
+
+            // Change one value and verify the other is unchanged.
+            pair0.Value = "othervalue";
+            Assert.Equal("othervalue", pair0.Value);
+            Assert.Equal("value", pair1.Value);
+            Assert.Throws<InvalidOperationException>(() => { pair1.Value = "othervalue"; });
+        }
+
+        [Fact]
+        public void CopyFromReadOnly_NameAndValue_CopiedAsNonReadOnly()
+        {
+            var pair0 = new NameValueHeaderValue("name", "value");
+            var pair1 = pair0.CopyAsReadOnly();
+            var pair2 = pair1.Copy();
+            Assert.NotSame(pair0, pair1);
+            Assert.Same(pair0.Name.Value, pair1.Name.Value);
+            Assert.Same(pair0.Value.Value, pair1.Value.Value);
+
+            // Change one value and verify the other is unchanged.
+            pair2.Value = "othervalue";
+            Assert.Equal("othervalue", pair2.Value);
+            Assert.Equal("value", pair1.Value);
+        }
+
+        [Fact]
+        public void Value_CallSetterWithInvalidValues_Throw()
+        {
+            // Just verify that the setter calls the same validation the ctor invokes.
+            Assert.Throws<FormatException>(() => { var x = new NameValueHeaderValue("name"); x.Value = " x "; });
+            Assert.Throws<FormatException>(() => { var x = new NameValueHeaderValue("name"); x.Value = "x y"; });
+        }
+
+        [Fact]
+        public void ToString_UseNoValueAndTokenAndQuotedStringValues_SerializedCorrectly()
+        {
+            var nameValue = new NameValueHeaderValue("text", "token");
+            Assert.Equal("text=token", nameValue.ToString());
+
+            nameValue.Value = "\"quoted string\"";
+            Assert.Equal("text=\"quoted string\"", nameValue.ToString());
+
+            nameValue.Value = null;
+            Assert.Equal("text", nameValue.ToString());
+
+            nameValue.Value = string.Empty;
+            Assert.Equal("text", nameValue.ToString());
+        }
+
+        [Fact]
+        public void GetHashCode_ValuesUseDifferentValues_HashDiffersAccordingToRfc()
+        {
+            var nameValue1 = new NameValueHeaderValue("text");
+            var nameValue2 = new NameValueHeaderValue("text");
+
+            nameValue1.Value = null;
+            nameValue2.Value = null;
+            Assert.Equal(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+
+            nameValue1.Value = "token";
+            nameValue2.Value = null;
+            Assert.NotEqual(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+
+            nameValue1.Value = "token";
+            nameValue2.Value = string.Empty;
+            Assert.NotEqual(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+
+            nameValue1.Value = null;
+            nameValue2.Value = string.Empty;
+            Assert.Equal(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+
+            nameValue1.Value = "token";
+            nameValue2.Value = "TOKEN";
+            Assert.Equal(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+
+            nameValue1.Value = "token";
+            nameValue2.Value = "token";
+            Assert.Equal(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+
+            nameValue1.Value = "\"quoted string\"";
+            nameValue2.Value = "\"QUOTED STRING\"";
+            Assert.NotEqual(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+
+            nameValue1.Value = "\"quoted string\"";
+            nameValue2.Value = "\"quoted string\"";
+            Assert.Equal(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+        }
+
+        [Fact]
+        public void GetHashCode_NameUseDifferentCasing_HashDiffersAccordingToRfc()
+        {
+            var nameValue1 = new NameValueHeaderValue("text");
+            var nameValue2 = new NameValueHeaderValue("TEXT");
+            Assert.Equal(nameValue1.GetHashCode(), nameValue2.GetHashCode());
+        }
+
+        [Fact]
+        public void Equals_ValuesUseDifferentValues_ValuesAreEqualOrDifferentAccordingToRfc()
+        {
+            var nameValue1 = new NameValueHeaderValue("text");
+            var nameValue2 = new NameValueHeaderValue("text");
+
+            nameValue1.Value = null;
+            nameValue2.Value = null;
+            Assert.True(nameValue1.Equals(nameValue2), "<null> vs. <null>.");
+
+            nameValue1.Value = "token";
+            nameValue2.Value = null;
+            Assert.False(nameValue1.Equals(nameValue2), "token vs. <null>.");
+
+            nameValue1.Value = null;
+            nameValue2.Value = "token";
+            Assert.False(nameValue1.Equals(nameValue2), "<null> vs. token.");
+
+            nameValue1.Value = string.Empty;
+            nameValue2.Value = "token";
+            Assert.False(nameValue1.Equals(nameValue2), "string.Empty vs. token.");
+
+            nameValue1.Value = null;
+            nameValue2.Value = string.Empty;
+            Assert.True(nameValue1.Equals(nameValue2), "<null> vs. string.Empty.");
+
+            nameValue1.Value = "token";
+            nameValue2.Value = "TOKEN";
+            Assert.True(nameValue1.Equals(nameValue2), "token vs. TOKEN.");
+
+            nameValue1.Value = "token";
+            nameValue2.Value = "token";
+            Assert.True(nameValue1.Equals(nameValue2), "token vs. token.");
+
+            nameValue1.Value = "\"quoted string\"";
+            nameValue2.Value = "\"QUOTED STRING\"";
+            Assert.False(nameValue1.Equals(nameValue2), "\"quoted string\" vs. \"QUOTED STRING\".");
+
+            nameValue1.Value = "\"quoted string\"";
+            nameValue2.Value = "\"quoted string\"";
+            Assert.True(nameValue1.Equals(nameValue2), "\"quoted string\" vs. \"quoted string\".");
+
+            Assert.False(nameValue1.Equals(null), "\"quoted string\" vs. <null>.");
+        }
+
+        [Fact]
+        public void Equals_NameUseDifferentCasing_ConsideredEqual()
+        {
+            var nameValue1 = new NameValueHeaderValue("text");
+            var nameValue2 = new NameValueHeaderValue("TEXT");
+            Assert.True(nameValue1.Equals(nameValue2), "text vs. TEXT.");
+        }
+
+        [Fact]
+        public void Parse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            CheckValidParse("  name = value    ", new NameValueHeaderValue("name", "value"));
+            CheckValidParse(" name", new NameValueHeaderValue("name"));
+            CheckValidParse(" name   ", new NameValueHeaderValue("name"));
+            CheckValidParse(" name=\"value\"", new NameValueHeaderValue("name", "\"value\""));
+            CheckValidParse("name=value", new NameValueHeaderValue("name", "value"));
+            CheckValidParse("name=\"quoted str\"", new NameValueHeaderValue("name", "\"quoted str\""));
+            CheckValidParse("name\t =va1ue", new NameValueHeaderValue("name", "va1ue"));
+            CheckValidParse("name= va*ue ", new NameValueHeaderValue("name", "va*ue"));
+            CheckValidParse("name=", new NameValueHeaderValue("name", ""));
+        }
+
+        [Fact]
+        public void Parse_SetOfInvalidValueStrings_Throws()
+        {
+            CheckInvalidParse("name[value");
+            CheckInvalidParse("name=value=");
+            CheckInvalidParse("name=会");
+            CheckInvalidParse("name==value");
+            CheckInvalidParse("name= va:ue");
+            CheckInvalidParse("=value");
+            CheckInvalidParse("name value");
+            CheckInvalidParse("name=,value");
+            CheckInvalidParse("会");
+            CheckInvalidParse(null);
+            CheckInvalidParse(string.Empty);
+            CheckInvalidParse("  ");
+            CheckInvalidParse("  ,,");
+            CheckInvalidParse(" , , name = value  ,  ");
+            CheckInvalidParse(" name,");
+            CheckInvalidParse(" ,name=\"value\"");
+        }
+
+        [Fact]
+        public void TryParse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            CheckValidTryParse("  name = value    ", new NameValueHeaderValue("name", "value"));
+            CheckValidTryParse(" name", new NameValueHeaderValue("name"));
+            CheckValidTryParse(" name=\"value\"", new NameValueHeaderValue("name", "\"value\""));
+            CheckValidTryParse("name=value", new NameValueHeaderValue("name", "value"));
+        }
+
+        [Fact]
+        public void TryParse_SetOfInvalidValueStrings_ReturnsFalse()
+        {
+            CheckInvalidTryParse("name[value");
+            CheckInvalidTryParse("name=value=");
+            CheckInvalidTryParse("name=会");
+            CheckInvalidTryParse("name==value");
+            CheckInvalidTryParse("=value");
+            CheckInvalidTryParse("name value");
+            CheckInvalidTryParse("name=,value");
+            CheckInvalidTryParse("会");
+            CheckInvalidTryParse(null);
+            CheckInvalidTryParse(string.Empty);
+            CheckInvalidTryParse("  ");
+            CheckInvalidTryParse("  ,,");
+            CheckInvalidTryParse(" , , name = value  ,  ");
+            CheckInvalidTryParse(" name,");
+            CheckInvalidTryParse(" ,name=\"value\"");
+        }
+
+        [Fact]
+        public void ParseList_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            var inputs = new[]
+            {
+                "",
+                "name=value1",
+                "",
+                " name = value2 ",
+                "\r\n name =value3\r\n ",
+                "name=\"value 4\"",
+                "name=\"value会5\"",
+                "name=value6,name=value7",
+                "name=\"value 8\", name= \"value 9\"",
+            };
+            var results = NameValueHeaderValue.ParseList(inputs);
+
+            var expectedResults = new[]
+            {
+                new NameValueHeaderValue("name", "value1"),
+                new NameValueHeaderValue("name", "value2"),
+                new NameValueHeaderValue("name", "value3"),
+                new NameValueHeaderValue("name", "\"value 4\""),
+                new NameValueHeaderValue("name", "\"value会5\""),
+                new NameValueHeaderValue("name", "value6"),
+                new NameValueHeaderValue("name", "value7"),
+                new NameValueHeaderValue("name", "\"value 8\""),
+                new NameValueHeaderValue("name", "\"value 9\""),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void ParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            var inputs = new[]
+            {
+                "",
+                "name=value1",
+                "",
+                " name = value2 ",
+                "\r\n name =value3\r\n ",
+                "name=\"value 4\"",
+                "name=\"value会5\"",
+                "name=value6,name=value7",
+                "name=\"value 8\", name= \"value 9\"",
+            };
+            var results = NameValueHeaderValue.ParseStrictList(inputs);
+
+            var expectedResults = new[]
+            {
+                new NameValueHeaderValue("name", "value1"),
+                new NameValueHeaderValue("name", "value2"),
+                new NameValueHeaderValue("name", "value3"),
+                new NameValueHeaderValue("name", "\"value 4\""),
+                new NameValueHeaderValue("name", "\"value会5\""),
+                new NameValueHeaderValue("name", "value6"),
+                new NameValueHeaderValue("name", "value7"),
+                new NameValueHeaderValue("name", "\"value 8\""),
+                new NameValueHeaderValue("name", "\"value 9\""),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void TryParseList_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            var inputs = new[]
+            {
+                "",
+                "name=value1",
+                "",
+                " name = value2 ",
+                "\r\n name =value3\r\n ",
+                "name=\"value 4\"",
+                "name=\"value会5\"",
+                "name=value6,name=value7",
+                "name=\"value 8\", name= \"value 9\"",
+            };
+            IList<NameValueHeaderValue> results;
+            Assert.True(NameValueHeaderValue.TryParseList(inputs, out results));
+
+            var expectedResults = new[]
+            {
+                new NameValueHeaderValue("name", "value1"),
+                new NameValueHeaderValue("name", "value2"),
+                new NameValueHeaderValue("name", "value3"),
+                new NameValueHeaderValue("name", "\"value 4\""),
+                new NameValueHeaderValue("name", "\"value会5\""),
+                new NameValueHeaderValue("name", "value6"),
+                new NameValueHeaderValue("name", "value7"),
+                new NameValueHeaderValue("name", "\"value 8\""),
+                new NameValueHeaderValue("name", "\"value 9\""),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void TryParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            var inputs = new[]
+            {
+                "",
+                "name=value1",
+                "",
+                " name = value2 ",
+                "\r\n name =value3\r\n ",
+                "name=\"value 4\"",
+                "name=\"value会5\"",
+                "name=value6,name=value7",
+                "name=\"value 8\", name= \"value 9\"",
+            };
+            IList<NameValueHeaderValue> results;
+            Assert.True(NameValueHeaderValue.TryParseStrictList(inputs, out results));
+
+            var expectedResults = new[]
+            {
+                new NameValueHeaderValue("name", "value1"),
+                new NameValueHeaderValue("name", "value2"),
+                new NameValueHeaderValue("name", "value3"),
+                new NameValueHeaderValue("name", "\"value 4\""),
+                new NameValueHeaderValue("name", "\"value会5\""),
+                new NameValueHeaderValue("name", "value6"),
+                new NameValueHeaderValue("name", "value7"),
+                new NameValueHeaderValue("name", "\"value 8\""),
+                new NameValueHeaderValue("name", "\"value 9\""),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void ParseList_WithSomeInvlaidValues_ExcludesInvalidValues()
+        {
+            var inputs = new[]
+            {
+                "",
+                "name1=value1",
+                "name2",
+                " name3 = 3, value a",
+                "name4 =value4, name5 = value5 b",
+                "name6=\"value 6",
+                "name7=\"value会7\"",
+                "name8=value8,name9=value9",
+                "name10=\"value 10\", name11= \"value 11\"",
+            };
+            var results = NameValueHeaderValue.ParseList(inputs);
+
+            var expectedResults = new[]
+            {
+                new NameValueHeaderValue("name1", "value1"),
+                new NameValueHeaderValue("name2"),
+                new NameValueHeaderValue("name3", "3"),
+                new NameValueHeaderValue("a"),
+                new NameValueHeaderValue("name4", "value4"),
+                new NameValueHeaderValue("b"),
+                new NameValueHeaderValue("6"),
+                new NameValueHeaderValue("name7", "\"value会7\""),
+                new NameValueHeaderValue("name8", "value8"),
+                new NameValueHeaderValue("name9", "value9"),
+                new NameValueHeaderValue("name10", "\"value 10\""),
+                new NameValueHeaderValue("name11", "\"value 11\""),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void ParseStrictList_WithSomeInvlaidValues_Throws()
+        {
+            var inputs = new[]
+            {
+                "",
+                "name1=value1",
+                "name2",
+                " name3 = 3, value a",
+                "name4 =value4, name5 = value5 b",
+                "name6=\"value 6",
+                "name7=\"value会7\"",
+                "name8=value8,name9=value9",
+                "name10=\"value 10\", name11= \"value 11\"",
+            };
+            Assert.Throws<FormatException>(() => NameValueHeaderValue.ParseStrictList(inputs));
+        }
+
+        [Fact]
+        public void TryParseList_WithSomeInvlaidValues_ExcludesInvalidValues()
+        {
+            var inputs = new[]
+            {
+                "",
+                "name1=value1",
+                "name2",
+                " name3 = 3, value a",
+                "name4 =value4, name5 = value5 b",
+                "name6=\"value 6",
+                "name7=\"value会7\"",
+                "name8=value8,name9=value9",
+                "name10=\"value 10\", name11= \"value 11\"",
+            };
+            IList<NameValueHeaderValue> results;
+            Assert.True(NameValueHeaderValue.TryParseList(inputs, out results));
+
+            var expectedResults = new[]
+            {
+                new NameValueHeaderValue("name1", "value1"),
+                new NameValueHeaderValue("name2"),
+                new NameValueHeaderValue("name3", "3"),
+                new NameValueHeaderValue("a"),
+                new NameValueHeaderValue("name4", "value4"),
+                new NameValueHeaderValue("b"),
+                new NameValueHeaderValue("6"),
+                new NameValueHeaderValue("name7", "\"value会7\""),
+                new NameValueHeaderValue("name8", "value8"),
+                new NameValueHeaderValue("name9", "value9"),
+                new NameValueHeaderValue("name10", "\"value 10\""),
+                new NameValueHeaderValue("name11", "\"value 11\""),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void TryParseStrictList_WithSomeInvlaidValues_ReturnsFalse()
+        {
+            var inputs = new[]
+            {
+                "",
+                "name1=value1",
+                "name2",
+                " name3 = 3, value a",
+                "name4 =value4, name5 = value5 b",
+                "name6=\"value 6",
+                "name7=\"value会7\"",
+                "name8=value8,name9=value9",
+                "name10=\"value 10\", name11= \"value 11\"",
+            };
+            IList<NameValueHeaderValue> results;
+            Assert.False(NameValueHeaderValue.TryParseStrictList(inputs, out results));
+        }
+
+        [Theory]
+        [InlineData("value", "value")]
+        [InlineData("\"value\"", "value")]
+        [InlineData("\"hello\\\\\"", "hello\\")]
+        [InlineData("\"hello\\\"\"", "hello\"")]
+        [InlineData("\"hello\\\"foo\\\\bar\\\\baz\\\\\"", "hello\"foo\\bar\\baz\\")]
+        [InlineData("\"quoted value\"", "quoted value")]
+        [InlineData("\"quoted\\\"valuewithquote\"", "quoted\"valuewithquote")]
+        [InlineData("\"hello\\\"", "hello\\")]
+        public void GetUnescapedValue_ReturnsExpectedValue(string input, string expected)
+        {
+            var header = new NameValueHeaderValue("test", input);
+
+            var actual = header.GetUnescapedValue();
+
+            Assert.Equal(expected, actual);
+        }
+
+        [Theory]
+        [InlineData("value", "value")]
+        [InlineData("23", "23")]
+        [InlineData(";;;", "\";;;\"")]
+        [InlineData("\"value\"", "\"value\"")]
+        [InlineData("\"assumes already encoded \\\"\"", "\"assumes already encoded \\\"\"")]
+        [InlineData("unquoted \"value", "\"unquoted \\\"value\"")]
+        [InlineData("value\\morevalues\\evenmorevalues", "\"value\\\\morevalues\\\\evenmorevalues\"")]
+        // We have to assume that the input needs to be quoted here
+        [InlineData("\"\"double quoted string\"\"", "\"\\\"\\\"double quoted string\\\"\\\"\"")]
+        [InlineData("\t", "\"\t\"")]
+        public void SetAndEscapeValue_ReturnsExpectedValue(string input, string expected)
+        {
+            var header = new NameValueHeaderValue("test");
+            header.SetAndEscapeValue(input);
+
+            var actual = header.Value;
+
+            Assert.Equal(expected, actual);
+        }
+
+
+        [Theory]
+        [InlineData("\n")]
+        [InlineData("\b")]
+        [InlineData("\r")]
+        public void SetAndEscapeValue_ThrowsOnInvalidValues(string input)
+        {
+            var header = new NameValueHeaderValue("test");
+            Assert.Throws<FormatException>(() => header.SetAndEscapeValue(input));
+        }
+
+        [Theory]
+        [InlineData("value")]
+        [InlineData("\"value\\\\morevalues\\\\evenmorevalues\"")]
+        [InlineData("\"quoted \\\"value\"")]
+        public void GetAndSetEncodeValueRoundTrip_ReturnsExpectedValue(string input)
+        {
+            var header = new NameValueHeaderValue("test");
+            header.Value = input;
+            var valueHeader = header.GetUnescapedValue();
+            header.SetAndEscapeValue(valueHeader);
+
+            var actual = header.Value;
+
+            Assert.Equal(input, actual);
+        }
+
+        [Theory]
+        [InlineData("val\\nue")]
+        [InlineData("val\\bue")]
+        public void OverescapingValuesDoNotRoundTrip(string input)
+        {
+            var header = new NameValueHeaderValue("test");
+            header.SetAndEscapeValue(input);
+            var valueHeader = header.GetUnescapedValue();
+
+            var actual = header.Value;
+
+            Assert.NotEqual(input, actual);
+        }
+
+
+        #region Helper methods
+
+        private void CheckValidParse(string input, NameValueHeaderValue expectedResult)
+        {
+            var result = NameValueHeaderValue.Parse(input);
+            Assert.Equal(expectedResult, result);
+        }
+
+        private void CheckInvalidParse(string input)
+        {
+            Assert.Throws<FormatException>(() => NameValueHeaderValue.Parse(input));
+        }
+
+        private void CheckValidTryParse(string input, NameValueHeaderValue expectedResult)
+        {
+            NameValueHeaderValue result = null;
+            Assert.True(NameValueHeaderValue.TryParse(input, out result));
+            Assert.Equal(expectedResult, result);
+        }
+
+        private void CheckInvalidTryParse(string input)
+        {
+            NameValueHeaderValue result = null;
+            Assert.False(NameValueHeaderValue.TryParse(input, out result));
+            Assert.Null(result);
+        }
+
+        private static void CheckValue(string value)
+        {
+            var nameValue = new NameValueHeaderValue("text", value);
+            Assert.Equal(value, nameValue.Value);
+        }
+
+        private static void AssertFormatException(string name, string value)
+        {
+            Assert.Throws<FormatException>(() => new NameValueHeaderValue(name, value));
+        }
+
+        #endregion
+    }
+}
diff --git a/src/Http/Headers/test/RangeConditionHeaderValueTest.cs b/src/Http/Headers/test/RangeConditionHeaderValueTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ce7c73997b8d3e09a9611549a2305898286c68c1
--- /dev/null
+++ b/src/Http/Headers/test/RangeConditionHeaderValueTest.cs
@@ -0,0 +1,174 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Xunit;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class RangeConditionHeaderValueTest
+    {
+        [Fact]
+        public void Ctor_EntityTagOverload_MatchExpectation()
+        {
+            var rangeCondition = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"x\""));
+            Assert.Equal(new EntityTagHeaderValue("\"x\""), rangeCondition.EntityTag);
+            Assert.Null(rangeCondition.LastModified);
+
+            EntityTagHeaderValue input = null;
+            Assert.Throws<ArgumentNullException>(() => new RangeConditionHeaderValue(input));
+        }
+
+        [Fact]
+        public void Ctor_EntityTagStringOverload_MatchExpectation()
+        {
+            var rangeCondition = new RangeConditionHeaderValue("\"y\"");
+            Assert.Equal(new EntityTagHeaderValue("\"y\""), rangeCondition.EntityTag);
+            Assert.Null(rangeCondition.LastModified);
+
+            Assert.Throws<ArgumentException>(() => new RangeConditionHeaderValue((string)null));
+        }
+
+        [Fact]
+        public void Ctor_DateOverload_MatchExpectation()
+        {
+            var rangeCondition = new RangeConditionHeaderValue(
+                new DateTimeOffset(2010, 7, 15, 12, 33, 57, TimeSpan.Zero));
+            Assert.Null(rangeCondition.EntityTag);
+            Assert.Equal(new DateTimeOffset(2010, 7, 15, 12, 33, 57, TimeSpan.Zero), rangeCondition.LastModified);
+        }
+
+        [Fact]
+        public void ToString_UseDifferentrangeConditions_AllSerializedCorrectly()
+        {
+            var rangeCondition = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"x\""));
+            Assert.Equal("\"x\"", rangeCondition.ToString());
+
+            rangeCondition = new RangeConditionHeaderValue(new DateTimeOffset(2010, 7, 15, 12, 33, 57, TimeSpan.Zero));
+            Assert.Equal("Thu, 15 Jul 2010 12:33:57 GMT", rangeCondition.ToString());
+        }
+
+        [Fact]
+        public void GetHashCode_UseSameAndDifferentrangeConditions_SameOrDifferentHashCodes()
+        {
+            var rangeCondition1 = new RangeConditionHeaderValue("\"x\"");
+            var rangeCondition2 = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"x\""));
+            var rangeCondition3 = new RangeConditionHeaderValue(
+                new DateTimeOffset(2010, 7, 15, 12, 33, 57, TimeSpan.Zero));
+            var rangeCondition4 = new RangeConditionHeaderValue(
+                new DateTimeOffset(2008, 8, 16, 13, 44, 10, TimeSpan.Zero));
+            var rangeCondition5 = new RangeConditionHeaderValue(
+                new DateTimeOffset(2010, 7, 15, 12, 33, 57, TimeSpan.Zero));
+            var rangeCondition6 = new RangeConditionHeaderValue(
+                new EntityTagHeaderValue("\"x\"", true));
+
+            Assert.Equal(rangeCondition1.GetHashCode(), rangeCondition2.GetHashCode());
+            Assert.NotEqual(rangeCondition1.GetHashCode(), rangeCondition3.GetHashCode());
+            Assert.NotEqual(rangeCondition3.GetHashCode(), rangeCondition4.GetHashCode());
+            Assert.Equal(rangeCondition3.GetHashCode(), rangeCondition5.GetHashCode());
+            Assert.NotEqual(rangeCondition1.GetHashCode(), rangeCondition6.GetHashCode());
+        }
+
+        [Fact]
+        public void Equals_UseSameAndDifferentRanges_EqualOrNotEqualNoExceptions()
+        {
+            var rangeCondition1 = new RangeConditionHeaderValue("\"x\"");
+            var rangeCondition2 = new RangeConditionHeaderValue(new EntityTagHeaderValue("\"x\""));
+            var rangeCondition3 = new RangeConditionHeaderValue(
+                new DateTimeOffset(2010, 7, 15, 12, 33, 57, TimeSpan.Zero));
+            var rangeCondition4 = new RangeConditionHeaderValue(
+                new DateTimeOffset(2008, 8, 16, 13, 44, 10, TimeSpan.Zero));
+            var rangeCondition5 = new RangeConditionHeaderValue(
+                new DateTimeOffset(2010, 7, 15, 12, 33, 57, TimeSpan.Zero));
+            var rangeCondition6 = new RangeConditionHeaderValue(
+                new EntityTagHeaderValue("\"x\"", true));
+
+            Assert.False(rangeCondition1.Equals(null), "\"x\" vs. <null>");
+            Assert.True(rangeCondition1.Equals(rangeCondition2), "\"x\" vs. \"x\"");
+            Assert.False(rangeCondition1.Equals(rangeCondition3), "\"x\" vs. date");
+            Assert.False(rangeCondition3.Equals(rangeCondition1), "date vs. \"x\"");
+            Assert.False(rangeCondition3.Equals(rangeCondition4), "date vs. different date");
+            Assert.True(rangeCondition3.Equals(rangeCondition5), "date vs. date");
+            Assert.False(rangeCondition1.Equals(rangeCondition6), "\"x\" vs. W/\"x\"");
+        }
+
+        [Fact]
+        public void Parse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            CheckValidParse("  \"x\" ", new RangeConditionHeaderValue("\"x\""));
+            CheckValidParse("  Sun, 06 Nov 1994 08:49:37 GMT ",
+                new RangeConditionHeaderValue(new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero)));
+            CheckValidParse("Wed, 09 Nov 1994 08:49:37 GMT",
+                new RangeConditionHeaderValue(new DateTimeOffset(1994, 11, 9, 8, 49, 37, TimeSpan.Zero)));
+            CheckValidParse(" W/ \"tag\" ", new RangeConditionHeaderValue(new EntityTagHeaderValue("\"tag\"", true)));
+            CheckValidParse(" w/\"tag\"", new RangeConditionHeaderValue(new EntityTagHeaderValue("\"tag\"", true)));
+            CheckValidParse("\"tag\"", new RangeConditionHeaderValue(new EntityTagHeaderValue("\"tag\"")));
+        }
+
+        [Theory]
+        [InlineData("\"x\" ,")] // no delimiter allowed
+        [InlineData("Sun, 06 Nov 1994 08:49:37 GMT ,")] // no delimiter allowed
+        [InlineData("\"x\" Sun, 06 Nov 1994 08:49:37 GMT")]
+        [InlineData("Sun, 06 Nov 1994 08:49:37 GMT \"x\"")]
+        [InlineData(null)]
+        [InlineData("")]
+        [InlineData(" Wed 09 Nov 1994 08:49:37 GMT")]
+        [InlineData("\"x")]
+        [InlineData("Wed, 09 Nov")]
+        [InlineData("W/Wed 09 Nov 1994 08:49:37 GMT")]
+        [InlineData("\"x\",")]
+        [InlineData("Wed 09 Nov 1994 08:49:37 GMT,")]
+        public void Parse_SetOfInvalidValueStrings_Throws(string input)
+        {
+            Assert.Throws<FormatException>(() => RangeConditionHeaderValue.Parse(input));
+        }
+
+        [Fact]
+        public void TryParse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            CheckValidTryParse("  \"x\" ", new RangeConditionHeaderValue("\"x\""));
+            CheckValidTryParse("  Sun, 06 Nov 1994 08:49:37 GMT ",
+                new RangeConditionHeaderValue(new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero)));
+            CheckValidTryParse(" W/ \"tag\" ", new RangeConditionHeaderValue(new EntityTagHeaderValue("\"tag\"", true)));
+            CheckValidTryParse(" w/\"tag\"", new RangeConditionHeaderValue(new EntityTagHeaderValue("\"tag\"", true)));
+            CheckValidTryParse("\"tag\"", new RangeConditionHeaderValue(new EntityTagHeaderValue("\"tag\"")));
+        }
+
+        [Theory]
+        [InlineData("\"x\" ,")] // no delimiter allowed
+        [InlineData("Sun, 06 Nov 1994 08:49:37 GMT ,")] // no delimiter allowed
+        [InlineData("\"x\" Sun, 06 Nov 1994 08:49:37 GMT")]
+        [InlineData("Sun, 06 Nov 1994 08:49:37 GMT \"x\"")]
+        [InlineData(null)]
+        [InlineData("")]
+        [InlineData(" Wed 09 Nov 1994 08:49:37 GMT")]
+        [InlineData("\"x")]
+        [InlineData("Wed, 09 Nov")]
+        [InlineData("W/Wed 09 Nov 1994 08:49:37 GMT")]
+        [InlineData("\"x\",")]
+        [InlineData("Wed 09 Nov 1994 08:49:37 GMT,")]
+        public void TryParse_SetOfInvalidValueStrings_ReturnsFalse(string input)
+        {
+            RangeConditionHeaderValue result = null;
+            Assert.False(RangeConditionHeaderValue.TryParse(input, out result));
+            Assert.Null(result);
+        }
+
+        #region Helper methods
+
+        private void CheckValidParse(string input, RangeConditionHeaderValue expectedResult)
+        {
+            var result = RangeConditionHeaderValue.Parse(input);
+            Assert.Equal(expectedResult, result);
+        }
+
+        private void CheckValidTryParse(string input, RangeConditionHeaderValue expectedResult)
+        {
+            RangeConditionHeaderValue result = null;
+            Assert.True(RangeConditionHeaderValue.TryParse(input, out result));
+            Assert.Equal(expectedResult, result);
+        }
+
+        #endregion
+    }
+}
diff --git a/src/Http/Headers/test/RangeHeaderValueTest.cs b/src/Http/Headers/test/RangeHeaderValueTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..92a1d72521fcbd57f45e7773b507369047fa70f2
--- /dev/null
+++ b/src/Http/Headers/test/RangeHeaderValueTest.cs
@@ -0,0 +1,183 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using Xunit;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class RangeHeaderValueTest
+    {
+        [Fact]
+        public void Ctor_InvalidRange_Throw()
+        {
+            Assert.Throws<ArgumentOutOfRangeException>(() => new RangeHeaderValue(5, 2));
+        }
+
+        [Fact]
+        public void Unit_GetAndSetValidAndInvalidValues_MatchExpectation()
+        {
+            var range = new RangeHeaderValue();
+            range.Unit = "myunit";
+            Assert.Equal("myunit", range.Unit);
+
+            Assert.Throws<ArgumentException>(() => range.Unit = null);
+            Assert.Throws<ArgumentException>(() => range.Unit = "");
+            Assert.Throws<FormatException>(() => range.Unit = " x");
+            Assert.Throws<FormatException>(() => range.Unit = "x ");
+            Assert.Throws<FormatException>(() => range.Unit = "x y");
+        }
+
+        [Fact]
+        public void ToString_UseDifferentRanges_AllSerializedCorrectly()
+        {
+            var range = new RangeHeaderValue();
+            range.Unit = "myunit";
+            range.Ranges.Add(new RangeItemHeaderValue(1, 3));
+            Assert.Equal("myunit=1-3", range.ToString());
+
+            range.Ranges.Add(new RangeItemHeaderValue(5, null));
+            range.Ranges.Add(new RangeItemHeaderValue(null, 17));
+            Assert.Equal("myunit=1-3, 5-, -17", range.ToString());
+        }
+
+        [Fact]
+        public void GetHashCode_UseSameAndDifferentRanges_SameOrDifferentHashCodes()
+        {
+            var range1 = new RangeHeaderValue(1, 2);
+            var range2 = new RangeHeaderValue(1, 2);
+            range2.Unit = "BYTES";
+            var range3 = new RangeHeaderValue(1, null);
+            var range4 = new RangeHeaderValue(null, 2);
+            var range5 = new RangeHeaderValue();
+            range5.Ranges.Add(new RangeItemHeaderValue(1, 2));
+            range5.Ranges.Add(new RangeItemHeaderValue(3, 4));
+            var range6 = new RangeHeaderValue();
+            range6.Ranges.Add(new RangeItemHeaderValue(3, 4)); // reverse order of range5
+            range6.Ranges.Add(new RangeItemHeaderValue(1, 2));
+
+            Assert.Equal(range1.GetHashCode(), range2.GetHashCode());
+            Assert.NotEqual(range1.GetHashCode(), range3.GetHashCode());
+            Assert.NotEqual(range1.GetHashCode(), range4.GetHashCode());
+            Assert.NotEqual(range1.GetHashCode(), range5.GetHashCode());
+            Assert.Equal(range5.GetHashCode(), range6.GetHashCode());
+        }
+
+        [Fact]
+        public void Equals_UseSameAndDifferentRanges_EqualOrNotEqualNoExceptions()
+        {
+            var range1 = new RangeHeaderValue(1, 2);
+            var range2 = new RangeHeaderValue(1, 2);
+            range2.Unit = "BYTES";
+            var range3 = new RangeHeaderValue(1, null);
+            var range4 = new RangeHeaderValue(null, 2);
+            var range5 = new RangeHeaderValue();
+            range5.Ranges.Add(new RangeItemHeaderValue(1, 2));
+            range5.Ranges.Add(new RangeItemHeaderValue(3, 4));
+            var range6 = new RangeHeaderValue();
+            range6.Ranges.Add(new RangeItemHeaderValue(3, 4)); // reverse order of range5
+            range6.Ranges.Add(new RangeItemHeaderValue(1, 2));
+            var range7 = new RangeHeaderValue(1, 2);
+            range7.Unit = "other";
+
+            Assert.False(range1.Equals(null), "bytes=1-2 vs. <null>");
+            Assert.True(range1.Equals(range2), "bytes=1-2 vs. BYTES=1-2");
+            Assert.False(range1.Equals(range3), "bytes=1-2 vs. bytes=1-");
+            Assert.False(range1.Equals(range4), "bytes=1-2 vs. bytes=-2");
+            Assert.False(range1.Equals(range5), "bytes=1-2 vs. bytes=1-2,3-4");
+            Assert.True(range5.Equals(range6), "bytes=1-2,3-4 vs. bytes=3-4,1-2");
+            Assert.False(range1.Equals(range7), "bytes=1-2 vs. other=1-2");
+        }
+
+        [Fact]
+        public void Parse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            CheckValidParse(" bytes=1-2 ", new RangeHeaderValue(1, 2));
+
+            var expected = new RangeHeaderValue();
+            expected.Unit = "custom";
+            expected.Ranges.Add(new RangeItemHeaderValue(null, 5));
+            expected.Ranges.Add(new RangeItemHeaderValue(1, 4));
+            CheckValidParse("custom = -  5 , 1 - 4 ,,", expected);
+
+            expected = new RangeHeaderValue();
+            expected.Unit = "custom";
+            expected.Ranges.Add(new RangeItemHeaderValue(1, 2));
+            CheckValidParse(" custom = 1 - 2", expected);
+
+            expected = new RangeHeaderValue();
+            expected.Ranges.Add(new RangeItemHeaderValue(1, 2));
+            expected.Ranges.Add(new RangeItemHeaderValue(3, null));
+            expected.Ranges.Add(new RangeItemHeaderValue(null, 4));
+            CheckValidParse("bytes =1-2,,3-, , ,-4,,", expected);
+        }
+
+        [Fact]
+        public void Parse_SetOfInvalidValueStrings_Throws()
+        {
+            CheckInvalidParse("bytes=1-2x"); // only delimiter ',' allowed after last range
+            CheckInvalidParse("x bytes=1-2");
+            CheckInvalidParse("bytes=1-2.4");
+            CheckInvalidParse(null);
+            CheckInvalidParse(string.Empty);
+
+            CheckInvalidParse("bytes=1");
+            CheckInvalidParse("bytes=");
+            CheckInvalidParse("bytes");
+            CheckInvalidParse("bytes 1-2");
+            CheckInvalidParse("bytes= ,,, , ,,");
+        }
+
+        [Fact]
+        public void TryParse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            CheckValidTryParse(" bytes=1-2 ", new RangeHeaderValue(1, 2));
+
+            var expected = new RangeHeaderValue();
+            expected.Unit = "custom";
+            expected.Ranges.Add(new RangeItemHeaderValue(null, 5));
+            expected.Ranges.Add(new RangeItemHeaderValue(1, 4));
+            CheckValidTryParse("custom = -  5 , 1 - 4 ,,", expected);
+        }
+
+        [Fact]
+        public void TryParse_SetOfInvalidValueStrings_ReturnsFalse()
+        {
+            CheckInvalidTryParse("bytes=1-2x"); // only delimiter ',' allowed after last range
+            CheckInvalidTryParse("x bytes=1-2");
+            CheckInvalidTryParse("bytes=1-2.4");
+            CheckInvalidTryParse(null);
+            CheckInvalidTryParse(string.Empty);
+        }
+
+        #region Helper methods
+
+        private void CheckValidParse(string input, RangeHeaderValue expectedResult)
+        {
+            var result = RangeHeaderValue.Parse(input);
+            Assert.Equal(expectedResult, result);
+        }
+
+        private void CheckInvalidParse(string input)
+        {
+            Assert.Throws<FormatException>(() => RangeHeaderValue.Parse(input));
+        }
+
+        private void CheckValidTryParse(string input, RangeHeaderValue expectedResult)
+        {
+            RangeHeaderValue result = null;
+            Assert.True(RangeHeaderValue.TryParse(input, out result));
+            Assert.Equal(expectedResult, result);
+        }
+
+        private void CheckInvalidTryParse(string input)
+        {
+            RangeHeaderValue result = null;
+            Assert.False(RangeHeaderValue.TryParse(input, out result));
+            Assert.Null(result);
+        }
+
+        #endregion
+    }
+}
diff --git a/src/Http/Headers/test/RangeItemHeaderValueTest.cs b/src/Http/Headers/test/RangeItemHeaderValueTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..95598f0a46f4bc83c221a5fdf91afecbfc15c425
--- /dev/null
+++ b/src/Http/Headers/test/RangeItemHeaderValueTest.cs
@@ -0,0 +1,162 @@
+// 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 Xunit;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class RangeItemHeaderValueTest
+    {
+        [Fact]
+        public void Ctor_BothValuesNull_Throw()
+        {
+            Assert.Throws<ArgumentException>(() => new RangeItemHeaderValue(null, null));
+        }
+
+        [Fact]
+        public void Ctor_FromValueNegative_Throw()
+        {
+            Assert.Throws<ArgumentOutOfRangeException>(() => new RangeItemHeaderValue(-1, null));
+        }
+
+        [Fact]
+        public void Ctor_FromGreaterThanToValue_Throw()
+        {
+            Assert.Throws<ArgumentOutOfRangeException>(() => new RangeItemHeaderValue(2, 1));
+        }
+
+        [Fact]
+        public void Ctor_ToValueNegative_Throw()
+        {
+            Assert.Throws<ArgumentOutOfRangeException>(() => new RangeItemHeaderValue(null, -1));
+        }
+
+        [Fact]
+        public void Ctor_ValidFormat_SuccessfullyCreated()
+        {
+            var rangeItem = new RangeItemHeaderValue(1, 2);
+            Assert.Equal(1, rangeItem.From);
+            Assert.Equal(2, rangeItem.To);
+        }
+
+        [Fact]
+        public void ToString_UseDifferentRangeItems_AllSerializedCorrectly()
+        {
+            // Make sure ToString() doesn't add any separators.
+            var rangeItem = new RangeItemHeaderValue(1000000000, 2000000000);
+            Assert.Equal("1000000000-2000000000", rangeItem.ToString());
+
+            rangeItem = new RangeItemHeaderValue(5, null);
+            Assert.Equal("5-", rangeItem.ToString());
+
+            rangeItem = new RangeItemHeaderValue(null, 10);
+            Assert.Equal("-10", rangeItem.ToString());
+        }
+
+        [Fact]
+        public void GetHashCode_UseSameAndDifferentRangeItems_SameOrDifferentHashCodes()
+        {
+            var rangeItem1 = new RangeItemHeaderValue(1, 2);
+            var rangeItem2 = new RangeItemHeaderValue(1, null);
+            var rangeItem3 = new RangeItemHeaderValue(null, 2);
+            var rangeItem4 = new RangeItemHeaderValue(2, 2);
+            var rangeItem5 = new RangeItemHeaderValue(1, 2);
+
+            Assert.NotEqual(rangeItem1.GetHashCode(), rangeItem2.GetHashCode());
+            Assert.NotEqual(rangeItem1.GetHashCode(), rangeItem3.GetHashCode());
+            Assert.NotEqual(rangeItem1.GetHashCode(), rangeItem4.GetHashCode());
+            Assert.Equal(rangeItem1.GetHashCode(), rangeItem5.GetHashCode());
+        }
+
+        [Fact]
+        public void Equals_UseSameAndDifferentRanges_EqualOrNotEqualNoExceptions()
+        {
+            var rangeItem1 = new RangeItemHeaderValue(1, 2);
+            var rangeItem2 = new RangeItemHeaderValue(1, null);
+            var rangeItem3 = new RangeItemHeaderValue(null, 2);
+            var rangeItem4 = new RangeItemHeaderValue(2, 2);
+            var rangeItem5 = new RangeItemHeaderValue(1, 2);
+
+            Assert.False(rangeItem1.Equals(rangeItem2), "1-2 vs. 1-.");
+            Assert.False(rangeItem2.Equals(rangeItem1), "1- vs. 1-2.");
+            Assert.False(rangeItem1.Equals(null), "1-2 vs. null.");
+            Assert.False(rangeItem1.Equals(rangeItem3), "1-2 vs. -2.");
+            Assert.False(rangeItem3.Equals(rangeItem1), "-2 vs. 1-2.");
+            Assert.False(rangeItem1.Equals(rangeItem4), "1-2 vs. 2-2.");
+            Assert.True(rangeItem1.Equals(rangeItem5), "1-2 vs. 1-2.");
+        }
+
+        [Fact]
+        public void TryParse_DifferentValidScenarios_AllReturnNonZero()
+        {
+            CheckValidTryParse("1-2", 1, 2);
+            CheckValidTryParse(" 1-2", 1, 2);
+            CheckValidTryParse("0-0", 0, 0);
+            CheckValidTryParse(" 1-", 1, null);
+            CheckValidTryParse(" -2", null, 2);
+
+            CheckValidTryParse(" 684684 - 123456789012345 ", 684684, 123456789012345);
+
+            // The separator doesn't matter. It only parses until the first non-whitespace
+            CheckValidTryParse(" 1 - 2 ,", 1, 2);
+
+            CheckValidTryParse(",,1-2, 3 -  , , -6 , ,,", new Tuple<long?, long?>(1, 2), new Tuple<long?, long?>(3, null),
+                new Tuple<long?, long?>(null, 6));
+            CheckValidTryParse("1-2,", new Tuple<long?, long?>(1, 2));
+            CheckValidTryParse("1-", new Tuple<long?, long?>(1, null));
+        }
+
+        [Theory]
+        [InlineData(null)]
+        [InlineData("")]
+        [InlineData(",,")]
+        [InlineData("1")]
+        [InlineData("1-2,3")]
+        [InlineData("1--2")]
+        [InlineData("1,-2")]
+        [InlineData("-")]
+        [InlineData("--")]
+        [InlineData("2-1")]
+        [InlineData("12345678901234567890123-")] // >>Int64.MaxValue
+        [InlineData("-12345678901234567890123")] // >>Int64.MaxValue
+        [InlineData("9999999999999999999-")] // 19-digit numbers outside the Int64 range.
+        [InlineData("-9999999999999999999")] // 19-digit numbers outside the Int64 range.
+        public void TryParse_DifferentInvalidScenarios_AllReturnFalse(string input)
+        {
+            RangeHeaderValue result;
+            Assert.False(RangeHeaderValue.TryParse("byte=" + input, out result));
+        }
+
+        private static void CheckValidTryParse(string input, long? expectedFrom, long? expectedTo)
+        {
+            RangeHeaderValue result;
+            Assert.True(RangeHeaderValue.TryParse("byte=" + input, out result), input);
+
+            var ranges = result.Ranges.ToArray();
+            Assert.Single(ranges);
+
+            var range = ranges.First();
+
+            Assert.Equal(expectedFrom, range.From);
+            Assert.Equal(expectedTo, range.To);
+        }
+
+        private static void CheckValidTryParse(string input, params Tuple<long?, long?>[] expectedRanges)
+        {
+            RangeHeaderValue result;
+            Assert.True(RangeHeaderValue.TryParse("byte=" + input, out result), input);
+
+            var ranges = result.Ranges.ToArray();
+            Assert.Equal(expectedRanges.Length, ranges.Length);
+
+            for (int i = 0; i < expectedRanges.Length; i++)
+            {
+                Assert.Equal(expectedRanges[i].Item1, ranges[i].From);
+                Assert.Equal(expectedRanges[i].Item2, ranges[i].To);
+            }
+        }
+    }
+}
diff --git a/src/Http/Headers/test/SetCookieHeaderValueTest.cs b/src/Http/Headers/test/SetCookieHeaderValueTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e7e8bf045a12c91fd982443669f5c5560d6bc4b3
--- /dev/null
+++ b/src/Http/Headers/test/SetCookieHeaderValueTest.cs
@@ -0,0 +1,429 @@
+// 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.Text;
+using Xunit;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class SetCookieHeaderValueTest
+    {
+        public static TheoryData<SetCookieHeaderValue, string> SetCookieHeaderDataSet
+        {
+            get
+            {
+                var dataset = new TheoryData<SetCookieHeaderValue, string>();
+                var header1 = new SetCookieHeaderValue("name1", "n1=v1&n2=v2&n3=v3")
+                {
+                    Domain = "domain1",
+                    Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
+                    SameSite = SameSiteMode.Strict,
+                    HttpOnly = true,
+                    MaxAge = TimeSpan.FromDays(1),
+                    Path = "path1",
+                    Secure = true
+                };
+                dataset.Add(header1, "name1=n1=v1&n2=v2&n3=v3; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite=strict; httponly");
+
+                var header2 = new SetCookieHeaderValue("name2", "");
+                dataset.Add(header2, "name2=");
+
+                var header3 = new SetCookieHeaderValue("name2", "value2");
+                dataset.Add(header3, "name2=value2");
+
+                var header4 = new SetCookieHeaderValue("name4", "value4")
+                {
+                    MaxAge = TimeSpan.FromDays(1),
+                };
+                dataset.Add(header4, "name4=value4; max-age=86400");
+
+                var header5 = new SetCookieHeaderValue("name5", "value5")
+                {
+                    Domain = "domain1",
+                    Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
+                };
+                dataset.Add(header5, "name5=value5; expires=Sun, 06 Nov 1994 08:49:37 GMT; domain=domain1");
+
+                var header6 = new SetCookieHeaderValue("name6", "value6")
+                {
+                    SameSite = SameSiteMode.Lax,
+                };
+                dataset.Add(header6, "name6=value6; samesite=lax");
+
+                var header7 = new SetCookieHeaderValue("name7", "value7")
+                {
+                    SameSite = SameSiteMode.None,
+                };
+                dataset.Add(header7, "name7=value7");
+
+
+                return dataset;
+            }
+        }
+
+        public static TheoryData<string> InvalidSetCookieHeaderDataSet
+        {
+            get
+            {
+                return new TheoryData<string>
+                {
+                    "expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1",
+                    "name=value; expires=Sun, 06 Nov 1994 08:49:37 ZZZ; max-age=86400; domain=domain1",
+                    "name=value; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=-86400; domain=domain1",
+                };
+            }
+        }
+
+        public static TheoryData<string> InvalidCookieNames
+        {
+            get
+            {
+                return new TheoryData<string>
+                {
+                    "<acb>",
+                    "{acb}",
+                    "[acb]",
+                    "\"acb\"",
+                    "a,b",
+                    "a;b",
+                    "a\\b",
+                };
+            }
+        }
+
+        public static TheoryData<string> InvalidCookieValues
+        {
+            get
+            {
+                return new TheoryData<string>
+                {
+                    { "\"" },
+                    { "a,b" },
+                    { "a;b" },
+                    { "a\\b" },
+                    { "\"abc" },
+                    { "a\"bc" },
+                    { "abc\"" },
+                };
+            }
+        }
+
+        public static TheoryData<IList<SetCookieHeaderValue>, string[]> ListOfSetCookieHeaderDataSet
+        {
+            get
+            {
+                var dataset = new TheoryData<IList<SetCookieHeaderValue>, string[]>();
+                var header1 = new SetCookieHeaderValue("name1", "n1=v1&n2=v2&n3=v3")
+                {
+                    Domain = "domain1",
+                    Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
+                    SameSite = SameSiteMode.Strict,
+                    HttpOnly = true,
+                    MaxAge = TimeSpan.FromDays(1),
+                    Path = "path1",
+                    Secure = true
+                };
+                var string1 = "name1=n1=v1&n2=v2&n3=v3; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite=strict; httponly";
+
+                var header2 = new SetCookieHeaderValue("name2", "value2");
+                var string2 = "name2=value2";
+
+                var header3 = new SetCookieHeaderValue("name3", "value3")
+                {
+                    MaxAge = TimeSpan.FromDays(1),
+                };
+                var string3 = "name3=value3; max-age=86400";
+
+                var header4 = new SetCookieHeaderValue("name4", "value4")
+                {
+                    Domain = "domain1",
+                    Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
+                };
+                var string4 = "name4=value4; expires=Sun, 06 Nov 1994 08:49:37 GMT; domain=domain1";
+
+                var header5 = new SetCookieHeaderValue("name5", "value5")
+                {
+                    SameSite = SameSiteMode.Lax
+                };
+                var string5a = "name5=value5; samesite=lax";
+                var string5b = "name5=value5; samesite=Lax";
+
+                var header6 = new SetCookieHeaderValue("name6", "value6")
+                {
+                    SameSite = SameSiteMode.Strict
+                };
+                var string6a = "name6=value6; samesite";
+                var string6b = "name6=value6; samesite=Strict";
+                var string6c = "name6=value6; samesite=invalid";
+
+                dataset.Add(new[] { header1 }.ToList(), new[] { string1 });
+                dataset.Add(new[] { header1, header1 }.ToList(), new[] { string1, string1 });
+                dataset.Add(new[] { header1, header1 }.ToList(), new[] { string1, null, "", " ", ",", " , ", string1 });
+                dataset.Add(new[] { header2 }.ToList(), new[] { string2 });
+                dataset.Add(new[] { header1, header2 }.ToList(), new[] { string1, string2 });
+                dataset.Add(new[] { header1, header2 }.ToList(), new[] { string1 + ", " + string2 });
+                dataset.Add(new[] { header2, header1 }.ToList(), new[] { string2 + ", " + string1 });
+                dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string1, string2, string3, string4 });
+                dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(",", string1, string2, string3, string4) });
+                dataset.Add(new[] { header5 }.ToList(), new[] { string5a });
+                dataset.Add(new[] { header5 }.ToList(), new[] { string5b });
+                dataset.Add(new[] { header6 }.ToList(), new[] { string6a });
+                dataset.Add(new[] { header6 }.ToList(), new[] { string6b });
+                dataset.Add(new[] { header6 }.ToList(), new[] { string6c });
+
+                return dataset;
+            }
+        }
+
+        public static TheoryData<IList<SetCookieHeaderValue>, string[]> ListWithInvalidSetCookieHeaderDataSet
+        {
+            get
+            {
+                var dataset = new TheoryData<IList<SetCookieHeaderValue>, string[]>();
+                var header1 = new SetCookieHeaderValue("name1", "n1=v1&n2=v2&n3=v3")
+                {
+                    Domain = "domain1",
+                    Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
+                    SameSite = SameSiteMode.Strict,
+                    HttpOnly = true,
+                    MaxAge = TimeSpan.FromDays(1),
+                    Path = "path1",
+                    Secure = true
+                };
+                var string1 = "name1=n1=v1&n2=v2&n3=v3; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite=Strict; httponly";
+
+                var header2 = new SetCookieHeaderValue("name2", "value2");
+                var string2 = "name2=value2";
+
+                var header3 = new SetCookieHeaderValue("name3", "value3")
+                {
+                    MaxAge = TimeSpan.FromDays(1),
+                };
+                var string3 = "name3=value3; max-age=86400";
+
+                var header4 = new SetCookieHeaderValue("name4", "value4")
+                {
+                    Domain = "domain1",
+                    Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
+                };
+                var string4 = "name4=value4; expires=Sun, 06 Nov 1994 08:49:37 GMT; domain=domain1;";
+
+                var invalidString1 = "ipt={\"v\":{\"L\":3},\"pt:{\"d\":3},\"ct\":{},\"_t\":44,\"_v\":\"2\"}";
+
+                var invalidHeader2a = new SetCookieHeaderValue("expires", "Sun");
+                var invalidHeader2b = new SetCookieHeaderValue("domain", "domain1");
+                var invalidString2 = "ipt={\"v\":{\"L\":3},\"pt\":{d\":3},\"ct\":{},\"_t\":44,\"_v\":\"2\"}; expires=Sun, 06 Nov 1994 08:49:37 GMT; domain=domain1";
+
+                var invalidHeader3 = new SetCookieHeaderValue("domain", "domain1")
+                {
+                    Expires = new DateTimeOffset(1994, 11, 6, 8, 49, 37, TimeSpan.Zero),
+                };
+                var invalidString3 = "ipt={\"v\":{\"L\":3},\"pt\":{\"d:3},\"ct\":{},\"_t\":44,\"_v\":\"2\"}; domain=domain1; expires=Sun, 06 Nov 1994 08:49:37 GMT";
+
+                dataset.Add(null, new[] { invalidString1 });
+                dataset.Add(new[] { invalidHeader2a, invalidHeader2b }.ToList(), new[] { invalidString2 });
+                dataset.Add(new[] { invalidHeader3 }.ToList(), new[] { invalidString3 });
+                dataset.Add(new[] { header1 }.ToList(), new[] { string1, invalidString1 });
+                dataset.Add(new[] { header1 }.ToList(), new[] { invalidString1, null, "", " ", ",", " , ", string1 });
+                dataset.Add(new[] { header1 }.ToList(), new[] { string1 + ", " + invalidString1 });
+                dataset.Add(new[] { header1 }.ToList(), new[] { invalidString1 + ", " + string1 });
+                dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { invalidString1, string1, string2, string3, string4 });
+                dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string1, invalidString1, string2, string3, string4 });
+                dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string1, string2, invalidString1, string3, string4 });
+                dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string1, string2, string3, invalidString1, string4 });
+                dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string1, string2, string3, string4, invalidString1 });
+                dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(",", invalidString1, string1, string2, string3, string4) });
+                dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(",", string1, invalidString1, string2, string3, string4) });
+                dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(",", string1, string2, invalidString1, string3, string4) });
+                dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(",", string1, string2, string3, invalidString1, string4) });
+                dataset.Add(new[] { header1, header2, header3, header4 }.ToList(), new[] { string.Join(",", string1, string2, string3, string4, invalidString1) });
+
+                return dataset;
+            }
+        }
+
+        [Fact]
+        public void SetCookieHeaderValue_CtorThrowsOnNullName()
+        {
+            Assert.Throws<ArgumentNullException>(() => new SetCookieHeaderValue(null, "value"));
+        }
+
+        [Theory]
+        [MemberData(nameof(InvalidCookieNames))]
+        public void SetCookieHeaderValue_CtorThrowsOnInvalidName(string name)
+        {
+            Assert.Throws<ArgumentException>(() => new SetCookieHeaderValue(name, "value"));
+        }
+
+        [Theory]
+        [MemberData(nameof(InvalidCookieValues))]
+        public void SetCookieHeaderValue_CtorThrowsOnInvalidValue(string value)
+        {
+            Assert.Throws<ArgumentException>(() => new SetCookieHeaderValue("name", value));
+        }
+
+        [Fact]
+        public void SetCookieHeaderValue_Ctor1_InitializesCorrectly()
+        {
+            var header = new SetCookieHeaderValue("cookie");
+            Assert.Equal("cookie", header.Name);
+            Assert.Equal(string.Empty, header.Value);
+        }
+
+        [Theory]
+        [InlineData("name", "")]
+        [InlineData("name", "value")]
+        [InlineData("name", "\"acb\"")]
+        public void SetCookieHeaderValue_Ctor2InitializesCorrectly(string name, string value)
+        {
+            var header = new SetCookieHeaderValue(name, value);
+            Assert.Equal(name, header.Name);
+            Assert.Equal(value, header.Value);
+        }
+
+        [Fact]
+        public void SetCookieHeaderValue_Value()
+        {
+            var cookie = new SetCookieHeaderValue("name");
+            Assert.Equal(string.Empty, cookie.Value);
+
+            cookie.Value = "value1";
+            Assert.Equal("value1", cookie.Value);
+        }
+
+        [Theory]
+        [MemberData(nameof(SetCookieHeaderDataSet))]
+        public void SetCookieHeaderValue_ToString(SetCookieHeaderValue input, string expectedValue)
+        {
+            Assert.Equal(expectedValue, input.ToString());
+        }
+
+        [Theory]
+        [MemberData(nameof(SetCookieHeaderDataSet))]
+        public void SetCookieHeaderValue_AppendToStringBuilder(SetCookieHeaderValue input, string expectedValue)
+        {
+            var builder = new StringBuilder();
+
+            input.AppendToStringBuilder(builder);
+
+            Assert.Equal(expectedValue, builder.ToString());
+        }
+
+        [Theory]
+        [MemberData(nameof(SetCookieHeaderDataSet))]
+        public void SetCookieHeaderValue_Parse_AcceptsValidValues(SetCookieHeaderValue cookie, string expectedValue)
+        {
+            var header = SetCookieHeaderValue.Parse(expectedValue);
+
+            Assert.Equal(cookie, header);
+            Assert.Equal(expectedValue, header.ToString());
+        }
+
+        [Theory]
+        [MemberData(nameof(SetCookieHeaderDataSet))]
+        public void SetCookieHeaderValue_TryParse_AcceptsValidValues(SetCookieHeaderValue cookie, string expectedValue)
+        {
+            Assert.True(SetCookieHeaderValue.TryParse(expectedValue, out var header));
+
+            Assert.Equal(cookie, header);
+            Assert.Equal(expectedValue, header.ToString());
+        }
+
+        [Theory]
+        [MemberData(nameof(InvalidSetCookieHeaderDataSet))]
+        public void SetCookieHeaderValue_Parse_RejectsInvalidValues(string value)
+        {
+            Assert.Throws<FormatException>(() => SetCookieHeaderValue.Parse(value));
+        }
+
+        [Theory]
+        [MemberData(nameof(InvalidSetCookieHeaderDataSet))]
+        public void SetCookieHeaderValue_TryParse_RejectsInvalidValues(string value)
+        {
+            Assert.False(SetCookieHeaderValue.TryParse(value, out var _));
+        }
+
+        [Theory]
+        [MemberData(nameof(ListOfSetCookieHeaderDataSet))]
+        public void SetCookieHeaderValue_ParseList_AcceptsValidValues(IList<SetCookieHeaderValue> cookies, string[] input)
+        {
+            var results = SetCookieHeaderValue.ParseList(input);
+
+            Assert.Equal(cookies, results);
+        }
+
+        [Theory]
+        [MemberData(nameof(ListOfSetCookieHeaderDataSet))]
+        public void SetCookieHeaderValue_TryParseList_AcceptsValidValues(IList<SetCookieHeaderValue> cookies, string[] input)
+        {
+            bool result = SetCookieHeaderValue.TryParseList(input, out var results);
+            Assert.True(result);
+
+            Assert.Equal(cookies, results);
+        }
+
+        [Theory]
+        [MemberData(nameof(ListOfSetCookieHeaderDataSet))]
+        public void SetCookieHeaderValue_ParseStrictList_AcceptsValidValues(IList<SetCookieHeaderValue> cookies, string[] input)
+        {
+            var results = SetCookieHeaderValue.ParseStrictList(input);
+
+            Assert.Equal(cookies, results);
+        }
+
+        [Theory]
+        [MemberData(nameof(ListOfSetCookieHeaderDataSet))]
+        public void SetCookieHeaderValue_TryParseStrictList_AcceptsValidValues(IList<SetCookieHeaderValue> cookies, string[] input)
+        {
+            bool result = SetCookieHeaderValue.TryParseStrictList(input, out var results);
+            Assert.True(result);
+
+            Assert.Equal(cookies, results);
+        }
+
+        [Theory]
+        [MemberData(nameof(ListWithInvalidSetCookieHeaderDataSet))]
+        public void SetCookieHeaderValue_ParseList_ExcludesInvalidValues(IList<SetCookieHeaderValue> cookies, string[] input)
+        {
+            var results = SetCookieHeaderValue.ParseList(input);
+            // ParseList aways returns a list, even if empty. TryParseList may return null (via out).
+            Assert.Equal(cookies ?? new List<SetCookieHeaderValue>(), results);
+        }
+
+        [Theory]
+        [MemberData(nameof(ListWithInvalidSetCookieHeaderDataSet))]
+        public void SetCookieHeaderValue_TryParseList_ExcludesInvalidValues(IList<SetCookieHeaderValue> cookies, string[] input)
+        {
+            bool result = SetCookieHeaderValue.TryParseList(input, out var results);
+            Assert.Equal(cookies, results);
+            Assert.Equal(cookies?.Count > 0, result);
+        }
+
+        [Theory]
+        [MemberData(nameof(ListWithInvalidSetCookieHeaderDataSet))]
+        public void SetCookieHeaderValue_ParseStrictList_ThrowsForAnyInvalidValues(
+#pragma warning disable xUnit1026 // Theory methods should use all of their parameters
+            IList<SetCookieHeaderValue> cookies, 
+#pragma warning restore xUnit1026 // Theory methods should use all of their parameters
+            string[] input)
+        {
+            Assert.Throws<FormatException>(() => SetCookieHeaderValue.ParseStrictList(input));
+        }
+
+        [Theory]
+        [MemberData(nameof(ListWithInvalidSetCookieHeaderDataSet))]
+        public void SetCookieHeaderValue_TryParseStrictList_FailsForAnyInvalidValues(
+#pragma warning disable xUnit1026 // Theory methods should use all of their parameters
+            IList<SetCookieHeaderValue> cookies,
+#pragma warning restore xUnit1026 // Theory methods should use all of their parameters
+            string[] input)
+        {
+            bool result = SetCookieHeaderValue.TryParseStrictList(input, out var results);
+            Assert.Null(results);
+            Assert.False(result);
+        }
+    }
+}
diff --git a/src/Http/Headers/test/StringWithQualityHeaderValueComparerTest.cs b/src/Http/Headers/test/StringWithQualityHeaderValueComparerTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8cda48eef3413164af11a7fc45f6c38e03fe848a
--- /dev/null
+++ b/src/Http/Headers/test/StringWithQualityHeaderValueComparerTest.cs
@@ -0,0 +1,64 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class StringWithQualityHeaderValueComparerTest
+    {
+        public static TheoryData<string[], string[]> StringWithQualityHeaderValueComparerTestsBeforeAfterSortedValues
+        {
+            get
+            {
+                return new TheoryData<string[], string[]>
+                {
+                    {
+                        new string[]
+                        {
+                            "text",
+                            "text;q=1.0",
+                            "text",
+                            "text;q=0",
+                            "*;q=0.8",
+                            "*;q=1",
+                            "text;q=0.8",
+                            "*;q=0.6",
+                            "text;q=1.0",
+                            "*;q=0.4",
+                            "text;q=0.6",
+                        },
+                        new string[]
+                        {
+                            "text",
+                            "text;q=1.0",
+                            "text",
+                            "text;q=1.0",
+                            "*;q=1",
+                            "text;q=0.8",
+                            "*;q=0.8",
+                            "text;q=0.6",
+                            "*;q=0.6",
+                            "*;q=0.4",
+                            "text;q=0",
+                        }
+                    }
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(StringWithQualityHeaderValueComparerTestsBeforeAfterSortedValues))]
+        public void SortStringWithQualityHeaderValuesByQFactor_SortsCorrectly(IEnumerable<string> unsorted, IEnumerable<string> expectedSorted)
+        {
+            var unsortedValues = StringWithQualityHeaderValue.ParseList(unsorted.ToList());
+            var expectedSortedValues = StringWithQualityHeaderValue.ParseList(expectedSorted.ToList());
+
+            var actualSorted = unsortedValues.OrderByDescending(k => k, StringWithQualityHeaderValueComparer.QualityComparer).ToList();
+
+            Assert.True(expectedSortedValues.SequenceEqual(actualSorted));
+        }
+    }
+}
diff --git a/src/Http/Headers/test/StringWithQualityHeaderValueTest.cs b/src/Http/Headers/test/StringWithQualityHeaderValueTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..49ee58b93eb388ec6229e5d7fdd5fac3ee5874ee
--- /dev/null
+++ b/src/Http/Headers/test/StringWithQualityHeaderValueTest.cs
@@ -0,0 +1,498 @@
+// 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 Xunit;
+
+namespace Microsoft.Net.Http.Headers
+{
+    public class StringWithQualityHeaderValueTest
+    {
+        [Fact]
+        public void Ctor_StringOnlyOverload_MatchExpectation()
+        {
+            var value = new StringWithQualityHeaderValue("token");
+            Assert.Equal("token", value.Value);
+            Assert.Null(value.Quality);
+
+            Assert.Throws<ArgumentException>(() => new StringWithQualityHeaderValue(null));
+            Assert.Throws<ArgumentException>(() => new StringWithQualityHeaderValue(""));
+            Assert.Throws<FormatException>(() => new StringWithQualityHeaderValue("in valid"));
+        }
+
+        [Fact]
+        public void Ctor_StringWithQualityOverload_MatchExpectation()
+        {
+            var value = new StringWithQualityHeaderValue("token", 0.5);
+            Assert.Equal("token", value.Value);
+            Assert.Equal(0.5, value.Quality);
+
+            Assert.Throws<ArgumentException>(() => new StringWithQualityHeaderValue(null, 0.1));
+            Assert.Throws<ArgumentException>(() => new StringWithQualityHeaderValue("", 0.1));
+            Assert.Throws<FormatException>(() => new StringWithQualityHeaderValue("in valid", 0.1));
+
+            Assert.Throws<ArgumentOutOfRangeException>(() => new StringWithQualityHeaderValue("t", 1.1));
+            Assert.Throws<ArgumentOutOfRangeException>(() => new StringWithQualityHeaderValue("t", -0.1));
+        }
+
+        [Fact]
+        public void ToString_UseDifferentValues_AllSerializedCorrectly()
+        {
+            var value = new StringWithQualityHeaderValue("token");
+            Assert.Equal("token", value.ToString());
+
+            value = new StringWithQualityHeaderValue("token", 0.1);
+            Assert.Equal("token; q=0.1", value.ToString());
+
+            value = new StringWithQualityHeaderValue("token", 0);
+            Assert.Equal("token; q=0.0", value.ToString());
+
+            value = new StringWithQualityHeaderValue("token", 1);
+            Assert.Equal("token; q=1.0", value.ToString());
+
+            // Note that the quality value gets rounded
+            value = new StringWithQualityHeaderValue("token", 0.56789);
+            Assert.Equal("token; q=0.568", value.ToString());
+        }
+
+        [Fact]
+        public void GetHashCode_UseSameAndDifferentValues_SameOrDifferentHashCodes()
+        {
+            var value1 = new StringWithQualityHeaderValue("t", 0.123);
+            var value2 = new StringWithQualityHeaderValue("t", 0.123);
+            var value3 = new StringWithQualityHeaderValue("T", 0.123);
+            var value4 = new StringWithQualityHeaderValue("t");
+            var value5 = new StringWithQualityHeaderValue("x", 0.123);
+            var value6 = new StringWithQualityHeaderValue("t", 0.5);
+            var value7 = new StringWithQualityHeaderValue("t", 0.1234);
+            var value8 = new StringWithQualityHeaderValue("T");
+            var value9 = new StringWithQualityHeaderValue("x");
+
+            Assert.Equal(value1.GetHashCode(), value2.GetHashCode());
+            Assert.Equal(value1.GetHashCode(), value3.GetHashCode());
+            Assert.NotEqual(value1.GetHashCode(), value4.GetHashCode());
+            Assert.NotEqual(value1.GetHashCode(), value5.GetHashCode());
+            Assert.NotEqual(value1.GetHashCode(), value6.GetHashCode());
+            Assert.NotEqual(value1.GetHashCode(), value7.GetHashCode());
+            Assert.Equal(value4.GetHashCode(), value8.GetHashCode());
+            Assert.NotEqual(value4.GetHashCode(), value9.GetHashCode());
+        }
+
+        [Fact]
+        public void Equals_UseSameAndDifferentRanges_EqualOrNotEqualNoExceptions()
+        {
+            var value1 = new StringWithQualityHeaderValue("t", 0.123);
+            var value2 = new StringWithQualityHeaderValue("t", 0.123);
+            var value3 = new StringWithQualityHeaderValue("T", 0.123);
+            var value4 = new StringWithQualityHeaderValue("t");
+            var value5 = new StringWithQualityHeaderValue("x", 0.123);
+            var value6 = new StringWithQualityHeaderValue("t", 0.5);
+            var value7 = new StringWithQualityHeaderValue("t", 0.1234);
+            var value8 = new StringWithQualityHeaderValue("T");
+            var value9 = new StringWithQualityHeaderValue("x");
+
+            Assert.False(value1.Equals(null), "t; q=0.123 vs. <null>");
+            Assert.True(value1.Equals(value2), "t; q=0.123 vs. t; q=0.123");
+            Assert.True(value1.Equals(value3), "t; q=0.123 vs. T; q=0.123");
+            Assert.False(value1.Equals(value4), "t; q=0.123 vs. t");
+            Assert.False(value4.Equals(value1), "t vs. t; q=0.123");
+            Assert.False(value1.Equals(value5), "t; q=0.123 vs. x; q=0.123");
+            Assert.False(value1.Equals(value6), "t; q=0.123 vs. t; q=0.5");
+            Assert.False(value1.Equals(value7), "t; q=0.123 vs. t; q=0.1234");
+            Assert.True(value4.Equals(value8), "t vs. T");
+            Assert.False(value4.Equals(value9), "t vs. T");
+        }
+
+        [Fact]
+        public void Parse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            CheckValidParse("text", new StringWithQualityHeaderValue("text"));
+            CheckValidParse("text;q=0.5", new StringWithQualityHeaderValue("text", 0.5));
+            CheckValidParse("text ; q = 0.5", new StringWithQualityHeaderValue("text", 0.5));
+            CheckValidParse("\r\n text ; q = 0.5 ", new StringWithQualityHeaderValue("text", 0.5));
+            CheckValidParse("  text  ", new StringWithQualityHeaderValue("text"));
+            CheckValidParse(" \r\n text \r\n ; \r\n q = 0.123", new StringWithQualityHeaderValue("text", 0.123));
+            CheckValidParse(" text ; q = 0.123 ", new StringWithQualityHeaderValue("text", 0.123));
+            CheckValidParse("text;q=1 ", new StringWithQualityHeaderValue("text", 1));
+            CheckValidParse("*", new StringWithQualityHeaderValue("*"));
+            CheckValidParse("*;q=0.7", new StringWithQualityHeaderValue("*", 0.7));
+            CheckValidParse(" t", new StringWithQualityHeaderValue("t"));
+            CheckValidParse("t;q=0.", new StringWithQualityHeaderValue("t", 0));
+            CheckValidParse("t;q=1.", new StringWithQualityHeaderValue("t", 1));
+            CheckValidParse("t;q=1.000", new StringWithQualityHeaderValue("t", 1));
+            CheckValidParse("t;q=0.12345678", new StringWithQualityHeaderValue("t", 0.12345678));
+            CheckValidParse("t ;  q  =   0", new StringWithQualityHeaderValue("t", 0));
+            CheckValidParse("iso-8859-5", new StringWithQualityHeaderValue("iso-8859-5"));
+            CheckValidParse("unicode-1-1; q=0.8", new StringWithQualityHeaderValue("unicode-1-1", 0.8));
+        }
+
+        [Theory]
+        [InlineData("text,")]
+        [InlineData("\r\n text ; q = 0.5, next_text  ")]
+        [InlineData("  text,next_text  ")]
+        [InlineData(" ,, text, , ,next")]
+        [InlineData(" ,, text, , ,")]
+        [InlineData(", \r\n text \r\n ; \r\n q = 0.123")]
+        [InlineData("teäxt")]
+        [InlineData("text会")]
+        [InlineData("会")]
+        [InlineData("t;q=会")]
+        [InlineData("t;q=")]
+        [InlineData("t;q")]
+        [InlineData("t;会=1")]
+        [InlineData("t;q会=1")]
+        [InlineData("t y")]
+        [InlineData("t;q=1 y")]
+        [InlineData(null)]
+        [InlineData("")]
+        [InlineData("  ")]
+        [InlineData("  ,,")]
+        [InlineData("t;q=-1")]
+        [InlineData("t;q=1.00001")]
+        [InlineData("t;")]
+        [InlineData("t;;q=1")]
+        [InlineData("t;q=a")]
+        [InlineData("t;qa")]
+        [InlineData("t;q1")]
+        [InlineData("integer_part_too_long;q=01")]
+        [InlineData("integer_part_too_long;q=01.0")]
+        [InlineData("decimal_part_too_long;q=0.123456789")]
+        [InlineData("decimal_part_too_long;q=0.123456789 ")]
+        [InlineData("no_integer_part;q=.1")]
+        public void Parse_SetOfInvalidValueStrings_Throws(string input)
+        {
+            Assert.Throws<FormatException>(() => StringWithQualityHeaderValue.Parse(input));
+        }
+
+        [Fact]
+        public void TryParse_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            CheckValidTryParse("text", new StringWithQualityHeaderValue("text"));
+            CheckValidTryParse("text;q=0.5", new StringWithQualityHeaderValue("text", 0.5));
+            CheckValidTryParse("text ; q = 0.5", new StringWithQualityHeaderValue("text", 0.5));
+            CheckValidTryParse("\r\n text ; q = 0.5 ", new StringWithQualityHeaderValue("text", 0.5));
+            CheckValidTryParse("  text  ", new StringWithQualityHeaderValue("text"));
+            CheckValidTryParse(" \r\n text \r\n ; \r\n q = 0.123", new StringWithQualityHeaderValue("text", 0.123));
+        }
+
+        [Fact]
+        public void TryParse_SetOfInvalidValueStrings_ReturnsFalse()
+        {
+            CheckInvalidTryParse("text,");
+            CheckInvalidTryParse("\r\n text ; q = 0.5, next_text  ");
+            CheckInvalidTryParse("  text,next_text  ");
+            CheckInvalidTryParse(" ,, text, , ,next");
+            CheckInvalidTryParse(" ,, text, , ,");
+            CheckInvalidTryParse(", \r\n text \r\n ; \r\n q = 0.123");
+            CheckInvalidTryParse("teäxt");
+            CheckInvalidTryParse("text会");
+            CheckInvalidTryParse("会");
+            CheckInvalidTryParse("t;q=会");
+            CheckInvalidTryParse("t;q=");
+            CheckInvalidTryParse("t;q");
+            CheckInvalidTryParse("t;会=1");
+            CheckInvalidTryParse("t;q会=1");
+            CheckInvalidTryParse("t y");
+            CheckInvalidTryParse("t;q=1 y");
+
+            CheckInvalidTryParse(null);
+            CheckInvalidTryParse(string.Empty);
+            CheckInvalidTryParse("  ");
+            CheckInvalidTryParse("  ,,");
+        }
+
+        [Fact]
+        public void ParseList_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            var inputs = new[]
+            {
+                "",
+                "text1",
+                "text2,",
+                "textA,textB",
+                "text3;q=0.5",
+                "text4;q=0.5,",
+                " text5 ; q = 0.50 ",
+                "\r\n text6 ; q = 0.05 ",
+                "text7,text8;q=0.5",
+                " text9 , text10 ; q = 0.5 ",
+            };
+            IList<StringWithQualityHeaderValue> results = StringWithQualityHeaderValue.ParseList(inputs);
+
+            var expectedResults = new[]
+            {
+                new StringWithQualityHeaderValue("text1"),
+                new StringWithQualityHeaderValue("text2"),
+                new StringWithQualityHeaderValue("textA"),
+                new StringWithQualityHeaderValue("textB"),
+                new StringWithQualityHeaderValue("text3", 0.5),
+                new StringWithQualityHeaderValue("text4", 0.5),
+                new StringWithQualityHeaderValue("text5", 0.5),
+                new StringWithQualityHeaderValue("text6", 0.05),
+                new StringWithQualityHeaderValue("text7"),
+                new StringWithQualityHeaderValue("text8", 0.5),
+                new StringWithQualityHeaderValue("text9"),
+                new StringWithQualityHeaderValue("text10", 0.5),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void ParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            var inputs = new[]
+            {
+                "",
+                "text1",
+                "text2,",
+                "textA,textB",
+                "text3;q=0.5",
+                "text4;q=0.5,",
+                " text5 ; q = 0.50 ",
+                "\r\n text6 ; q = 0.05 ",
+                "text7,text8;q=0.5",
+                " text9 , text10 ; q = 0.5 ",
+            };
+            IList<StringWithQualityHeaderValue> results = StringWithQualityHeaderValue.ParseStrictList(inputs);
+
+            var expectedResults = new[]
+            {
+                new StringWithQualityHeaderValue("text1"),
+                new StringWithQualityHeaderValue("text2"),
+                new StringWithQualityHeaderValue("textA"),
+                new StringWithQualityHeaderValue("textB"),
+                new StringWithQualityHeaderValue("text3", 0.5),
+                new StringWithQualityHeaderValue("text4", 0.5),
+                new StringWithQualityHeaderValue("text5", 0.5),
+                new StringWithQualityHeaderValue("text6", 0.05),
+                new StringWithQualityHeaderValue("text7"),
+                new StringWithQualityHeaderValue("text8", 0.5),
+                new StringWithQualityHeaderValue("text9"),
+                new StringWithQualityHeaderValue("text10", 0.5),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void TryParseList_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            var inputs = new[]
+            {
+                "",
+                "text1",
+                "text2,",
+                "textA,textB",
+                "text3;q=0.5",
+                "text4;q=0.5,",
+                " text5 ; q = 0.50 ",
+                "\r\n text6 ; q = 0.05 ",
+                "text7,text8;q=0.5",
+                " text9 , text10 ; q = 0.5 ",
+            };
+            IList<StringWithQualityHeaderValue> results;
+            Assert.True(StringWithQualityHeaderValue.TryParseList(inputs, out results));
+
+            var expectedResults = new[]
+            {
+                new StringWithQualityHeaderValue("text1"),
+                new StringWithQualityHeaderValue("text2"),
+                new StringWithQualityHeaderValue("textA"),
+                new StringWithQualityHeaderValue("textB"),
+                new StringWithQualityHeaderValue("text3", 0.5),
+                new StringWithQualityHeaderValue("text4", 0.5),
+                new StringWithQualityHeaderValue("text5", 0.5),
+                new StringWithQualityHeaderValue("text6", 0.05),
+                new StringWithQualityHeaderValue("text7"),
+                new StringWithQualityHeaderValue("text8", 0.5),
+                new StringWithQualityHeaderValue("text9"),
+                new StringWithQualityHeaderValue("text10", 0.5),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void TryParseStrictList_SetOfValidValueStrings_ParsedCorrectly()
+        {
+            var inputs = new[]
+            {
+                "",
+                "text1",
+                "text2,",
+                "textA,textB",
+                "text3;q=0.5",
+                "text4;q=0.5,",
+                " text5 ; q = 0.50 ",
+                "\r\n text6 ; q = 0.05 ",
+                "text7,text8;q=0.5",
+                " text9 , text10 ; q = 0.5 ",
+            };
+            IList<StringWithQualityHeaderValue> results;
+            Assert.True(StringWithQualityHeaderValue.TryParseStrictList(inputs, out results));
+
+            var expectedResults = new[]
+            {
+                new StringWithQualityHeaderValue("text1"),
+                new StringWithQualityHeaderValue("text2"),
+                new StringWithQualityHeaderValue("textA"),
+                new StringWithQualityHeaderValue("textB"),
+                new StringWithQualityHeaderValue("text3", 0.5),
+                new StringWithQualityHeaderValue("text4", 0.5),
+                new StringWithQualityHeaderValue("text5", 0.5),
+                new StringWithQualityHeaderValue("text6", 0.05),
+                new StringWithQualityHeaderValue("text7"),
+                new StringWithQualityHeaderValue("text8", 0.5),
+                new StringWithQualityHeaderValue("text9"),
+                new StringWithQualityHeaderValue("text10", 0.5),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void ParseList_WithSomeInvlaidValues_IgnoresInvalidValues()
+        {
+            var inputs = new[]
+            {
+                "",
+                "text1",
+                "text 1",
+                "text2",
+                "\"text 2\",",
+                "text3;q=0.5",
+                "text4;q=0.5, extra stuff",
+                " text5 ; q = 0.50 ",
+                "\r\n text6 ; q = 0.05 ",
+                "text7,text8;q=0.5",
+                " text9 , text10 ; q = 0.5 ",
+            };
+            var results = StringWithQualityHeaderValue.ParseList(inputs);
+
+            var expectedResults = new[]
+            {
+                new StringWithQualityHeaderValue("text1"),
+                new StringWithQualityHeaderValue("1"),
+                new StringWithQualityHeaderValue("text2"),
+                new StringWithQualityHeaderValue("text3", 0.5),
+                new StringWithQualityHeaderValue("text4", 0.5),
+                new StringWithQualityHeaderValue("stuff"),
+                new StringWithQualityHeaderValue("text5", 0.5),
+                new StringWithQualityHeaderValue("text6", 0.05),
+                new StringWithQualityHeaderValue("text7"),
+                new StringWithQualityHeaderValue("text8", 0.5),
+                new StringWithQualityHeaderValue("text9"),
+                new StringWithQualityHeaderValue("text10", 0.5),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void ParseStrictList_WithSomeInvlaidValues_Throws()
+        {
+            var inputs = new[]
+            {
+                "",
+                "text1",
+                "text 1",
+                "text2",
+                "\"text 2\",",
+                "text3;q=0.5",
+                "text4;q=0.5, extra stuff",
+                " text5 ; q = 0.50 ",
+                "\r\n text6 ; q = 0.05 ",
+                "text7,text8;q=0.5",
+                " text9 , text10 ; q = 0.5 ",
+            };
+            Assert.Throws<FormatException>(() => StringWithQualityHeaderValue.ParseStrictList(inputs));
+        }
+
+        [Fact]
+        public void TryParseList_WithSomeInvlaidValues_IgnoresInvalidValues()
+        {
+            var inputs = new[]
+            {
+                "",
+                "text1",
+                "text 1",
+                "text2",
+                "\"text 2\",",
+                "text3;q=0.5",
+                "text4;q=0.5, extra stuff",
+                " text5 ; q = 0.50 ",
+                "\r\n text6 ; q = 0.05 ",
+                "text7,text8;q=0.5",
+                " text9 , text10 ; q = 0.5 ",
+            };
+            IList<StringWithQualityHeaderValue> results;
+            Assert.True(StringWithQualityHeaderValue.TryParseList(inputs, out results));
+
+            var expectedResults = new[]
+            {
+                new StringWithQualityHeaderValue("text1"),
+                new StringWithQualityHeaderValue("1"),
+                new StringWithQualityHeaderValue("text2"),
+                new StringWithQualityHeaderValue("text3", 0.5),
+                new StringWithQualityHeaderValue("text4", 0.5),
+                new StringWithQualityHeaderValue("stuff"),
+                new StringWithQualityHeaderValue("text5", 0.5),
+                new StringWithQualityHeaderValue("text6", 0.05),
+                new StringWithQualityHeaderValue("text7"),
+                new StringWithQualityHeaderValue("text8", 0.5),
+                new StringWithQualityHeaderValue("text9"),
+                new StringWithQualityHeaderValue("text10", 0.5),
+            }.ToList();
+
+            Assert.Equal(expectedResults, results);
+        }
+
+        [Fact]
+        public void TryParseStrictList_WithSomeInvlaidValues_ReturnsFalse()
+        {
+            var inputs = new[]
+            {
+                "",
+                "text1",
+                "text 1",
+                "text2",
+                "\"text 2\",",
+                "text3;q=0.5",
+                "text4;q=0.5, extra stuff",
+                " text5 ; q = 0.50 ",
+                "\r\n text6 ; q = 0.05 ",
+                "text7,text8;q=0.5",
+                " text9 , text10 ; q = 0.5 ",
+            };
+            IList<StringWithQualityHeaderValue> results;
+            Assert.False(StringWithQualityHeaderValue.TryParseStrictList(inputs, out results));
+        }
+
+        #region Helper methods
+
+        private void CheckValidParse(string input, StringWithQualityHeaderValue expectedResult)
+        {
+            var result = StringWithQualityHeaderValue.Parse(input);
+            Assert.Equal(expectedResult, result);
+        }
+
+        private void CheckValidTryParse(string input, StringWithQualityHeaderValue expectedResult)
+        {
+            StringWithQualityHeaderValue result = null;
+            Assert.True(StringWithQualityHeaderValue.TryParse(input, out result));
+            Assert.Equal(expectedResult, result);
+        }
+
+        private void CheckInvalidTryParse(string input)
+        {
+            StringWithQualityHeaderValue result = null;
+            Assert.False(StringWithQualityHeaderValue.TryParse(input, out result));
+            Assert.Null(result);
+        }
+
+        #endregion
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/Authentication/AuthenticateInfo.cs b/src/Http/Http.Abstractions/src/Authentication/AuthenticateInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9e8e3fd53760dfec918df29f82196a3c3954c1d9
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Authentication/AuthenticateInfo.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 System.Security.Claims;
+
+namespace Microsoft.AspNetCore.Http.Authentication
+{
+    /// <summary>
+    /// Used to store the results of an Authenticate call.
+    /// </summary>
+    public class AuthenticateInfo
+    {
+        /// <summary>
+        /// The <see cref="ClaimsPrincipal"/>.
+        /// </summary>
+        public ClaimsPrincipal Principal { get; set; }
+
+        /// <summary>
+        /// The <see cref="AuthenticationProperties"/>.
+        /// </summary>
+        public AuthenticationProperties Properties { get; set; }
+
+        /// <summary>
+        /// The <see cref="AuthenticationDescription"/>.
+        /// </summary>
+        public AuthenticationDescription Description { get; set; }
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/Authentication/AuthenticationDescription.cs b/src/Http/Http.Abstractions/src/Authentication/AuthenticationDescription.cs
new file mode 100644
index 0000000000000000000000000000000000000000..fb0a073f0bbfbf56bf600b140eb53cc838669d02
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Authentication/AuthenticationDescription.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 System.Collections.Generic;
+using System.Globalization;
+
+namespace Microsoft.AspNetCore.Http.Authentication
+{
+    /// <summary>
+    /// Contains information describing an authentication provider.
+    /// </summary>
+    public class AuthenticationDescription
+    {
+        private const string DisplayNamePropertyKey = "DisplayName";
+        private const string AuthenticationSchemePropertyKey = "AuthenticationScheme";
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AuthenticationDescription"/> class
+        /// </summary>
+        public AuthenticationDescription()
+            : this(items: null)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AuthenticationDescription"/> class
+        /// </summary>
+        /// <param name="items"></param>
+        public AuthenticationDescription(IDictionary<string, object> items)
+        {
+            Items = items ?? new Dictionary<string, object>(StringComparer.Ordinal); ;
+        }
+
+        /// <summary>
+        /// Contains metadata about the authentication provider.
+        /// </summary>
+        public IDictionary<string, object> Items { get; }
+
+        /// <summary>
+        /// Gets or sets the name used to reference the authentication middleware instance.
+        /// </summary>
+        public string AuthenticationScheme
+        {
+            get { return GetString(AuthenticationSchemePropertyKey); }
+            set { Items[AuthenticationSchemePropertyKey] = value; }
+        }
+
+        /// <summary>
+        /// Gets or sets the display name for the authentication provider.
+        /// </summary>
+        public string DisplayName
+        {
+            get { return GetString(DisplayNamePropertyKey); }
+            set { Items[DisplayNamePropertyKey] = value; }
+        }
+
+        private string GetString(string name)
+        {
+            object value;
+            if (Items.TryGetValue(name, out value))
+            {
+                return Convert.ToString(value, CultureInfo.InvariantCulture);
+            }
+            return null;
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/Authentication/AuthenticationManager.cs b/src/Http/Http.Abstractions/src/Authentication/AuthenticationManager.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b2916522a5e3e209675359bcfa3ce01657c07cb7
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Authentication/AuthenticationManager.cs
@@ -0,0 +1,132 @@
+// 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.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features.Authentication;
+
+namespace Microsoft.AspNetCore.Http.Authentication
+{
+    [Obsolete("This is obsolete and will be removed in a future version. See https://go.microsoft.com/fwlink/?linkid=845470.")]
+    public abstract class AuthenticationManager
+    {
+        /// <summary>
+        /// Constant used to represent the automatic scheme
+        /// </summary>
+        public const string AutomaticScheme = "Automatic";
+
+        public abstract HttpContext HttpContext { get; }
+
+        public abstract IEnumerable<AuthenticationDescription> GetAuthenticationSchemes();
+
+        public abstract Task<AuthenticateInfo> GetAuthenticateInfoAsync(string authenticationScheme);
+
+        // Will remove once callees have been updated
+        public abstract Task AuthenticateAsync(AuthenticateContext context);
+
+        public virtual async Task<ClaimsPrincipal> AuthenticateAsync(string authenticationScheme)
+        {
+            return (await GetAuthenticateInfoAsync(authenticationScheme))?.Principal;
+        }
+
+        public virtual Task ChallengeAsync()
+        {
+            return ChallengeAsync(properties: null);
+        }
+
+        public virtual Task ChallengeAsync(AuthenticationProperties properties)
+        {
+            return ChallengeAsync(authenticationScheme: AutomaticScheme, properties: properties);
+        }
+
+        public virtual Task ChallengeAsync(string authenticationScheme)
+        {
+            if (string.IsNullOrEmpty(authenticationScheme))
+            {
+                throw new ArgumentException(nameof(authenticationScheme));
+            }
+
+            return ChallengeAsync(authenticationScheme: authenticationScheme, properties: null);
+        }
+
+        // Leave it up to authentication handler to do the right thing for the challenge
+        public virtual Task ChallengeAsync(string authenticationScheme, AuthenticationProperties properties)
+        {
+            if (string.IsNullOrEmpty(authenticationScheme))
+            {
+                throw new ArgumentException(nameof(authenticationScheme));
+            }
+
+            return ChallengeAsync(authenticationScheme, properties, ChallengeBehavior.Automatic);
+        }
+
+        public virtual Task SignInAsync(string authenticationScheme, ClaimsPrincipal principal)
+        {
+            if (string.IsNullOrEmpty(authenticationScheme))
+            {
+                throw new ArgumentException(nameof(authenticationScheme));
+            }
+
+            if (principal == null)
+            {
+                throw new ArgumentNullException(nameof(principal));
+            }
+
+            return SignInAsync(authenticationScheme, principal, properties: null);
+        }
+
+        /// <summary>
+        /// Creates a challenge for the authentication manager with <see cref="ChallengeBehavior.Forbidden"/>.
+        /// </summary>
+        /// <returns>A <see cref="Task"/> that represents the asynchronous challenge operation.</returns>
+        public virtual Task ForbidAsync()
+            => ForbidAsync(AutomaticScheme, properties: null);
+
+        public virtual Task ForbidAsync(string authenticationScheme)
+        {
+            if (authenticationScheme == null)
+            {
+                throw new ArgumentNullException(nameof(authenticationScheme));
+            }
+
+            return ForbidAsync(authenticationScheme, properties: null);
+        }
+
+        // Deny access (typically a 403)
+        public virtual Task ForbidAsync(string authenticationScheme, AuthenticationProperties properties)
+        {
+            if (authenticationScheme == null)
+            {
+                throw new ArgumentNullException(nameof(authenticationScheme));
+            }
+
+            return ChallengeAsync(authenticationScheme, properties, ChallengeBehavior.Forbidden);
+        }
+
+        /// <summary>
+        /// Creates a challenge for the authentication manager with <see cref="ChallengeBehavior.Forbidden"/>.
+        /// </summary>
+        /// <param name="properties">Additional arbitrary values which may be used by particular authentication types.</param>
+        /// <returns>A <see cref="Task"/> that represents the asynchronous challenge operation.</returns>
+        public virtual Task ForbidAsync(AuthenticationProperties properties)
+            => ForbidAsync(AutomaticScheme, properties);
+
+        public abstract Task ChallengeAsync(string authenticationScheme, AuthenticationProperties properties, ChallengeBehavior behavior);
+
+        public abstract Task SignInAsync(string authenticationScheme, ClaimsPrincipal principal, AuthenticationProperties properties);
+
+        public virtual Task SignOutAsync(string authenticationScheme)
+        {
+            if (authenticationScheme == null)
+            {
+                throw new ArgumentNullException(nameof(authenticationScheme));
+            }
+
+            return SignOutAsync(authenticationScheme, properties: null);
+        }
+
+        public abstract Task SignOutAsync(string authenticationScheme, AuthenticationProperties properties);
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/Authentication/AuthenticationProperties.cs b/src/Http/Http.Abstractions/src/Authentication/AuthenticationProperties.cs
new file mode 100644
index 0000000000000000000000000000000000000000..881b24fff5e7c3f962f3d33a1cdf3d54a8836394
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Authentication/AuthenticationProperties.cs
@@ -0,0 +1,197 @@
+// 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.Globalization;
+
+namespace Microsoft.AspNetCore.Http.Authentication
+{
+    /// <summary>
+    /// Dictionary used to store state values about the authentication session.
+    /// </summary>
+    public class AuthenticationProperties
+    {
+        internal const string IssuedUtcKey = ".issued";
+        internal const string ExpiresUtcKey = ".expires";
+        internal const string IsPersistentKey = ".persistent";
+        internal const string RedirectUriKey = ".redirect";
+        internal const string RefreshKey = ".refresh";
+        internal const string UtcDateTimeFormat = "r";
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AuthenticationProperties"/> class
+        /// </summary>
+        public AuthenticationProperties()
+            : this(items: null)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="AuthenticationProperties"/> class
+        /// </summary>
+        /// <param name="items"></param>
+        public AuthenticationProperties(IDictionary<string, string> items)
+        {
+            Items = items ?? new Dictionary<string, string>(StringComparer.Ordinal);
+        }
+
+        /// <summary>
+        /// State values about the authentication session.
+        /// </summary>
+        public IDictionary<string, string> Items { get; }
+
+        /// <summary>
+        /// Gets or sets whether the authentication session is persisted across multiple requests.
+        /// </summary>
+        public bool IsPersistent
+        {
+            get { return Items.ContainsKey(IsPersistentKey); }
+            set
+            {
+                if (Items.ContainsKey(IsPersistentKey))
+                {
+                    if (!value)
+                    {
+                        Items.Remove(IsPersistentKey);
+                    }
+                }
+                else
+                {
+                    if (value)
+                    {
+                        Items.Add(IsPersistentKey, string.Empty);
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the full path or absolute URI to be used as an HTTP redirect response value.
+        /// </summary>
+        public string RedirectUri
+        {
+            get
+            {
+                string value;
+                return Items.TryGetValue(RedirectUriKey, out value) ? value : null;
+            }
+            set
+            {
+                if (value != null)
+                {
+                    Items[RedirectUriKey] = value;
+                }
+                else
+                {
+                    if (Items.ContainsKey(RedirectUriKey))
+                    {
+                        Items.Remove(RedirectUriKey);
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the time at which the authentication ticket was issued.
+        /// </summary>
+        public DateTimeOffset? IssuedUtc
+        {
+            get
+            {
+                string value;
+                if (Items.TryGetValue(IssuedUtcKey, out value))
+                {
+                    DateTimeOffset dateTimeOffset;
+                    if (DateTimeOffset.TryParseExact(value, UtcDateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTimeOffset))
+                    {
+                        return dateTimeOffset;
+                    }
+                }
+                return null;
+            }
+            set
+            {
+                if (value.HasValue)
+                {
+                    Items[IssuedUtcKey] = value.Value.ToString(UtcDateTimeFormat, CultureInfo.InvariantCulture);
+                }
+                else
+                {
+                    if (Items.ContainsKey(IssuedUtcKey))
+                    {
+                        Items.Remove(IssuedUtcKey);
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the time at which the authentication ticket expires.
+        /// </summary>
+        public DateTimeOffset? ExpiresUtc
+        {
+            get
+            {
+                string value;
+                if (Items.TryGetValue(ExpiresUtcKey, out value))
+                {
+                    DateTimeOffset dateTimeOffset;
+                    if (DateTimeOffset.TryParseExact(value, UtcDateTimeFormat, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out dateTimeOffset))
+                    {
+                        return dateTimeOffset;
+                    }
+                }
+                return null;
+            }
+            set
+            {
+                if (value.HasValue)
+                {
+                    Items[ExpiresUtcKey] = value.Value.ToString(UtcDateTimeFormat, CultureInfo.InvariantCulture);
+                }
+                else
+                {
+                    if (Items.ContainsKey(ExpiresUtcKey))
+                    {
+                        Items.Remove(ExpiresUtcKey);
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets if refreshing the authentication session should be allowed.
+        /// </summary>
+        public bool? AllowRefresh
+        {
+            get
+            {
+                string value;
+                if (Items.TryGetValue(RefreshKey, out value))
+                {
+                    bool refresh;
+                    if (bool.TryParse(value, out refresh))
+                    {
+                        return refresh;
+                    }
+                }
+                return null;
+            }
+            set
+            {
+                if (value.HasValue)
+                {
+                    Items[RefreshKey] = value.Value.ToString();
+                }
+                else
+                {
+                    if (Items.ContainsKey(RefreshKey))
+                    {
+                        Items.Remove(RefreshKey);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/ConnectionInfo.cs b/src/Http/Http.Abstractions/src/ConnectionInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d4cab49afea28b86b9fec6a6eb0c8f3af5201fd9
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/ConnectionInfo.cs
@@ -0,0 +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.Net;
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public abstract class ConnectionInfo
+    {
+        /// <summary>
+        /// Gets or sets a unique identifier to represent this connection.
+        /// </summary>
+        public abstract string Id { get; set; }
+
+        public abstract IPAddress RemoteIpAddress { get; set; }
+
+        public abstract int RemotePort { get; set; }
+
+        public abstract IPAddress LocalIpAddress { get; set; }
+
+        public abstract int LocalPort { get; set; }
+
+        public abstract X509Certificate2 ClientCertificate { get; set; }
+
+        public abstract Task<X509Certificate2> GetClientCertificateAsync(CancellationToken cancellationToken = new CancellationToken());
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/CookieBuilder.cs b/src/Http/Http.Abstractions/src/CookieBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ce89e5b0541963345fcea75d44bd7efd50f9054a
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/CookieBuilder.cs
@@ -0,0 +1,114 @@
+// 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.Http.Abstractions;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Defines settings used to create a cookie.
+    /// </summary>
+    public class CookieBuilder
+    {
+        private string _name;
+
+        /// <summary>
+        /// The name of the cookie.
+        /// </summary>
+        public virtual string Name
+        {
+            get => _name;
+            set => _name = !string.IsNullOrEmpty(value)
+                ? value
+                : throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(value));
+        }
+
+        /// <summary>
+        /// The cookie path.
+        /// </summary>
+        /// <remarks>
+        /// Determines the value that will set on <seealso cref="CookieOptions.Path"/>.
+        /// </remarks>
+        public virtual string Path { get; set; }
+
+        /// <summary>
+        /// The domain to associate the cookie with.
+        /// </summary>
+        /// <remarks>
+        /// Determines the value that will set on <seealso cref="CookieOptions.Domain"/>.
+        /// </remarks>
+        public virtual string Domain { get; set; }
+
+        /// <summary>
+        /// Indicates whether a cookie is accessible by client-side script.
+        /// </summary>
+        /// <remarks>
+        /// Determines the value that will set on <seealso cref="CookieOptions.HttpOnly"/>.
+        /// </remarks>
+        public virtual bool HttpOnly { get; set; }
+
+        /// <summary>
+        /// The SameSite attribute of the cookie. The default value is <see cref="SameSiteMode.Lax"/>
+        /// </summary>
+        /// <remarks>
+        /// Determines the value that will set on <seealso cref="CookieOptions.SameSite"/>.
+        /// </remarks>
+        public virtual SameSiteMode SameSite { get; set; } = SameSiteMode.Lax;
+
+        /// <summary>
+        /// The policy that will be used to determine <seealso cref="CookieOptions.Secure"/>.
+        /// This is determined from the <see cref="HttpContext"/> passed to <see cref="Build(HttpContext, DateTimeOffset)"/>.
+        /// </summary>
+        public virtual CookieSecurePolicy SecurePolicy { get; set; }
+
+        /// <summary>
+        /// Gets or sets the lifespan of a cookie.
+        /// </summary>
+        public virtual TimeSpan? Expiration { get; set; }
+
+        /// <summary>
+        /// Gets or sets the max-age for the cookie.
+        /// </summary>
+        public virtual TimeSpan? MaxAge { get; set; }
+
+        /// <summary>
+        /// Indicates if this cookie is essential for the application to function correctly. If true then
+        /// consent policy checks may be bypassed. The default value is false.
+        /// </summary>
+        public virtual bool IsEssential { get; set; }
+
+        /// <summary>
+        /// Creates the cookie options from the given <paramref name="context"/>.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/>.</param>
+        /// <returns>The cookie options.</returns>
+        public CookieOptions Build(HttpContext context) => Build(context, DateTimeOffset.Now);
+
+        /// <summary>
+        /// Creates the cookie options from the given <paramref name="context"/> with an expiration based on <paramref name="expiresFrom"/> and <see cref="Expiration"/>.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/>.</param>
+        /// <param name="expiresFrom">The time to use as the base for computing <seealso cref="CookieOptions.Expires" />.</param>
+        /// <returns>The cookie options.</returns>
+        public virtual CookieOptions Build(HttpContext context, DateTimeOffset expiresFrom)
+        {
+            if (context == null)
+            {
+                throw new ArgumentNullException(nameof(context));
+            }
+
+            return new CookieOptions
+            {
+                Path = Path ?? "/",
+                SameSite = SameSite,
+                HttpOnly = HttpOnly,
+                MaxAge = MaxAge,
+                Domain = Domain,
+                IsEssential = IsEssential,
+                Secure = SecurePolicy == CookieSecurePolicy.Always || (SecurePolicy == CookieSecurePolicy.SameAsRequest && context.Request.IsHttps),
+                Expires = Expiration.HasValue ? expiresFrom.Add(Expiration.Value) : default(DateTimeOffset?)
+            };
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/CookieSecurePolicy.cs b/src/Http/Http.Abstractions/src/CookieSecurePolicy.cs
new file mode 100644
index 0000000000000000000000000000000000000000..af32d851b0e97ffcd71efbdae94a7fd3c8a73fe3
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/CookieSecurePolicy.cs
@@ -0,0 +1,34 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Determines how cookie security properties are set.
+    /// </summary>
+    public enum CookieSecurePolicy
+    {
+        /// <summary>
+        /// If the URI that provides the cookie is HTTPS, then the cookie will only be returned to the server on 
+        /// subsequent HTTPS requests. Otherwise if the URI that provides the cookie is HTTP, then the cookie will 
+        /// be returned to the server on all HTTP and HTTPS requests. This is the default value because it ensures
+        /// HTTPS for all authenticated requests on deployed servers, and also supports HTTP for localhost development 
+        /// and for servers that do not have HTTPS support.
+        /// </summary>
+        SameAsRequest,
+
+        /// <summary>
+        /// Secure is always marked true. Use this value when your login page and all subsequent pages
+        /// requiring the authenticated identity are HTTPS. Local development will also need to be done with HTTPS urls.
+        /// </summary>
+        Always,
+
+        /// <summary>
+        /// Secure is not marked true. Use this value when your login page is HTTPS, but other pages
+        /// on the site which are HTTP also require authentication information. This setting is not recommended because
+        /// the authentication information provided with an HTTP request may be observed and used by other computers
+        /// on your local network or wireless connection.
+        /// </summary>
+        None,
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/Extensions/HeaderDictionaryExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/HeaderDictionaryExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5cc06484a28c3d648fdd7ea8537368d2417689ab
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Extensions/HeaderDictionaryExtensions.cs
@@ -0,0 +1,56 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Http.Internal;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public static class HeaderDictionaryExtensions
+    {
+        /// <summary>
+        /// Add new values. Each item remains a separate array entry.
+        /// </summary>
+        /// <param name="headers">The <see cref="IHeaderDictionary"/> to use.</param>
+        /// <param name="key">The header name.</param>
+        /// <param name="value">The header value.</param>
+        public static void Append(this IHeaderDictionary headers, string key, StringValues value)
+        {
+            ParsingHelpers.AppendHeaderUnmodified(headers, key, value);
+        }
+
+        /// <summary>
+        /// Quotes any values containing commas, and then comma joins all of the values with any existing values.
+        /// </summary>
+        /// <param name="headers">The <see cref="IHeaderDictionary"/> to use.</param>
+        /// <param name="key">The header name.</param>
+        /// <param name="values">The header values.</param>
+        public static void AppendCommaSeparatedValues(this IHeaderDictionary headers, string key, params string[] values)
+        {
+            ParsingHelpers.AppendHeaderJoined(headers, key, values);
+        }
+
+        /// <summary>
+        /// Get the associated values from the collection separated into individual values.
+        /// Quoted values will not be split, and the quotes will be removed.
+        /// </summary>
+        /// <param name="headers">The <see cref="IHeaderDictionary"/> to use.</param>
+        /// <param name="key">The header name.</param>
+        /// <returns>the associated values from the collection separated into individual values, or StringValues.Empty if the key is not present.</returns>
+        public static string[] GetCommaSeparatedValues(this IHeaderDictionary headers, string key)
+        {
+            return ParsingHelpers.GetHeaderSplit(headers, key).ToArray();
+        }
+
+        /// <summary>
+        /// Quotes any values containing commas, and then comma joins all of the values.
+        /// </summary>
+        /// <param name="headers">The <see cref="IHeaderDictionary"/> to use.</param>
+        /// <param name="key">The header name.</param>
+        /// <param name="values">The header values.</param>
+        public static void SetCommaSeparatedValues(this IHeaderDictionary headers, string key, params string[] values)
+        {
+            ParsingHelpers.SetHeaderJoined(headers, key, values);
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/Extensions/HttpResponseWritingExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/HttpResponseWritingExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0b24a7d4f48bdac959598b100e202e7cb2193f36
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Extensions/HttpResponseWritingExtensions.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.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Convenience methods for writing to the response.
+    /// </summary>
+    public static class HttpResponseWritingExtensions
+    {
+        /// <summary>
+        /// Writes the given text to the response body. UTF-8 encoding will be used.
+        /// </summary>
+        /// <param name="response">The <see cref="HttpResponse"/>.</param>
+        /// <param name="text">The text to write to the response.</param>
+        /// <param name="cancellationToken">Notifies when request operations should be cancelled.</param>
+        /// <returns>A task that represents the completion of the write operation.</returns>
+        public static Task WriteAsync(this HttpResponse response, string text, CancellationToken cancellationToken = default(CancellationToken))
+        {
+            if (response == null)
+            {
+                throw new ArgumentNullException(nameof(response));
+            }
+
+            if (text == null)
+            {
+                throw new ArgumentNullException(nameof(text));
+            }
+
+            return response.WriteAsync(text, Encoding.UTF8, cancellationToken);
+        }
+
+        /// <summary>
+        /// Writes the given text to the response body using the given encoding.
+        /// </summary>
+        /// <param name="response">The <see cref="HttpResponse"/>.</param>
+        /// <param name="text">The text to write to the response.</param>
+        /// <param name="encoding">The encoding to use.</param>
+        /// <param name="cancellationToken">Notifies when request operations should be cancelled.</param>
+        /// <returns>A task that represents the completion of the write operation.</returns>
+        public static Task WriteAsync(this HttpResponse response, string text, Encoding encoding, CancellationToken cancellationToken = default(CancellationToken))
+        {
+            if (response == null)
+            {
+                throw new ArgumentNullException(nameof(response));
+            }
+
+            if (text == null)
+            {
+                throw new ArgumentNullException(nameof(text));
+            }
+
+            if (encoding == null)
+            {
+                throw new ArgumentNullException(nameof(encoding));
+            }
+
+            byte[] data = encoding.GetBytes(text);
+            return response.Body.WriteAsync(data, 0, data.Length, cancellationToken);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Abstractions/src/Extensions/MapExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/MapExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..448e2c6f6d20be7e574f21fb8f1231b1b7cd22b0
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Extensions/MapExtensions.cs
@@ -0,0 +1,53 @@
+// 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.Http;
+using Microsoft.AspNetCore.Builder.Extensions;
+
+namespace Microsoft.AspNetCore.Builder
+{
+    /// <summary>
+    /// Extension methods for the <see cref="MapMiddleware"/>.
+    /// </summary>
+    public static class MapExtensions
+    {
+        /// <summary>
+        /// Branches the request pipeline based on matches of the given request path. If the request path starts with
+        /// the given path, the branch is executed.
+        /// </summary>
+        /// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
+        /// <param name="pathMatch">The request path to match.</param>
+        /// <param name="configuration">The branch to take for positive path matches.</param>
+        /// <returns>The <see cref="IApplicationBuilder"/> instance.</returns>
+        public static IApplicationBuilder Map(this IApplicationBuilder app, PathString pathMatch, Action<IApplicationBuilder> configuration)
+        {
+            if (app == null)
+            {
+                throw new ArgumentNullException(nameof(app));
+            }
+
+            if (configuration == null)
+            {
+                throw new ArgumentNullException(nameof(configuration));
+            }
+
+            if (pathMatch.HasValue && pathMatch.Value.EndsWith("/", StringComparison.Ordinal))
+            {
+                throw new ArgumentException("The path must not end with a '/'", nameof(pathMatch));
+            }
+
+            // create branch
+            var branchBuilder = app.New();
+            configuration(branchBuilder);
+            var branch = branchBuilder.Build();
+
+            var options = new MapOptions
+            {
+                Branch = branch,
+                PathMatch = pathMatch,
+            };
+            return app.Use(next => new MapMiddleware(next, options).Invoke);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Abstractions/src/Extensions/MapMiddleware.cs b/src/Http/Http.Abstractions/src/Extensions/MapMiddleware.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a4f67ce4a23986699c74335729d751e9714bee5e
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Extensions/MapMiddleware.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;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Builder.Extensions
+{
+    /// <summary>
+    /// Respresents a middleware that maps a request path to a sub-request pipeline.
+    /// </summary>
+    public class MapMiddleware
+    {
+        private readonly RequestDelegate _next;
+        private readonly MapOptions _options;
+
+        /// <summary>
+        /// Creates a new instace of <see cref="MapMiddleware"/>.
+        /// </summary>
+        /// <param name="next">The delegate representing the next middleware in the request pipeline.</param>
+        /// <param name="options">The middleware options.</param>
+        public MapMiddleware(RequestDelegate next, MapOptions options)
+        {
+            if (next == null)
+            {
+                throw new ArgumentNullException(nameof(next));
+            }
+
+            if (options == null)
+            {
+                throw new ArgumentNullException(nameof(options));
+            }
+
+            _next = next;
+            _options = options;
+        }
+
+        /// <summary>
+        /// Executes the middleware.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> for the current request.</param>
+        /// <returns>A task that represents the execution of this middleware.</returns>
+        public async Task Invoke(HttpContext context)
+        {
+            if (context == null)
+            {
+                throw new ArgumentNullException(nameof(context));
+            }
+
+            PathString matchedPath;
+            PathString remainingPath;
+
+            if (context.Request.Path.StartsWithSegments(_options.PathMatch, out matchedPath, out remainingPath))
+            {
+                // Update the path
+                var path = context.Request.Path;
+                var pathBase = context.Request.PathBase;
+                context.Request.PathBase = pathBase.Add(matchedPath);
+                context.Request.Path = remainingPath;
+
+                try
+                {
+                    await _options.Branch(context);
+                }
+                finally
+                {
+                    context.Request.PathBase = pathBase;
+                    context.Request.Path = path;
+                }
+            }
+            else
+            {
+                await _next(context);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Abstractions/src/Extensions/MapOptions.cs b/src/Http/Http.Abstractions/src/Extensions/MapOptions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..60adc743794216779b916387d0f70c0826034d9a
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Extensions/MapOptions.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Builder.Extensions
+{
+    /// <summary>
+    /// Options for the <see cref="MapMiddleware"/>.
+    /// </summary>
+    public class MapOptions
+    {
+        /// <summary>
+        /// The path to match.
+        /// </summary>
+        public PathString PathMatch { get; set; }
+
+        /// <summary>
+        /// The branch taken for a positive match.
+        /// </summary>
+        public RequestDelegate Branch { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Abstractions/src/Extensions/MapWhenExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/MapWhenExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..946379df26f29b5244cf523684706da65dd7f801
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Extensions/MapWhenExtensions.cs
@@ -0,0 +1,56 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Builder.Extensions;
+
+
+namespace Microsoft.AspNetCore.Builder
+{
+    using Predicate = Func<HttpContext, bool>;
+
+    /// <summary>
+    /// Extension methods for the <see cref="MapWhenMiddleware"/>.
+    /// </summary>
+    public static class MapWhenExtensions
+    {
+        /// <summary>
+        /// Branches the request pipeline based on the result of the given predicate.
+        /// </summary>
+        /// <param name="app"></param>
+        /// <param name="predicate">Invoked with the request environment to determine if the branch should be taken</param>
+        /// <param name="configuration">Configures a branch to take</param>
+        /// <returns></returns>
+        public static IApplicationBuilder MapWhen(this IApplicationBuilder app, Predicate predicate, Action<IApplicationBuilder> configuration)
+        {
+            if (app == null)
+            {
+                throw new ArgumentNullException(nameof(app));
+            }
+
+            if (predicate == null)
+            {
+                throw new ArgumentNullException(nameof(predicate));
+            }
+
+            if (configuration == null)
+            {
+                throw new ArgumentNullException(nameof(configuration));
+            }
+
+            // create branch
+            var branchBuilder = app.New();
+            configuration(branchBuilder);
+            var branch = branchBuilder.Build();
+
+            // put middleware in pipeline
+            var options = new MapWhenOptions
+            {
+                Predicate = predicate,
+                Branch = branch,
+            };
+            return app.Use(next => new MapWhenMiddleware(next, options).Invoke);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Abstractions/src/Extensions/MapWhenMiddleware.cs b/src/Http/Http.Abstractions/src/Extensions/MapWhenMiddleware.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b012626ba9f912c63078047417fad4ec97b4ebc7
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Extensions/MapWhenMiddleware.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.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Builder.Extensions
+{
+    /// <summary>
+    /// Respresents a middleware that runs a sub-request pipeline when a given predicate is matched.
+    /// </summary>
+    public class MapWhenMiddleware
+    {
+        private readonly RequestDelegate _next;
+        private readonly MapWhenOptions _options;
+
+        /// <summary>
+        /// Creates a new instance of <see cref="MapWhenMiddleware"/>.
+        /// </summary>
+        /// <param name="next">The delegate representing the next middleware in the request pipeline.</param>
+        /// <param name="options">The middleware options.</param>
+        public MapWhenMiddleware(RequestDelegate next, MapWhenOptions options)
+        {
+            if (next == null)
+            {
+                throw new ArgumentNullException(nameof(next));
+            }
+
+            if (options == null)
+            {
+                throw new ArgumentNullException(nameof(options));
+            }
+
+            _next = next;
+            _options = options;
+        }
+
+        /// <summary>
+        /// Executes the middleware.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> for the current request.</param>
+        /// <returns>A task that represents the execution of this middleware.</returns>
+        public async Task Invoke(HttpContext context)
+        {
+            if (context == null)
+            {
+                throw new ArgumentNullException(nameof(context));
+            }
+
+            if (_options.Predicate(context))
+            {
+                await _options.Branch(context);
+            }
+            else
+            {
+                await _next(context);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Abstractions/src/Extensions/MapWhenOptions.cs b/src/Http/Http.Abstractions/src/Extensions/MapWhenOptions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d18eb7f257a18bca4a0a40996883226a83e82af4
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Extensions/MapWhenOptions.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 Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Builder.Extensions
+{
+    /// <summary>
+    /// Options for the <see cref="MapWhenMiddleware"/>.
+    /// </summary>
+    public class MapWhenOptions
+    {
+        private Func<HttpContext, bool> _predicate;
+
+        /// <summary>
+        /// The user callback that determines if the branch should be taken.
+        /// </summary>
+        public Func<HttpContext, bool> Predicate
+        {
+            get
+            {
+                return _predicate;
+            }
+            set
+            {
+                if (value == null)
+                {
+                    throw new ArgumentNullException(nameof(value));
+                }
+
+                _predicate = value;
+            }
+        }
+
+        /// <summary>
+        /// The branch taken for a positive match.
+        /// </summary>
+        public RequestDelegate Branch { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Abstractions/src/Extensions/RunExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/RunExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1124043064273e8c1c140502ce748608c1ada4ea
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Extensions/RunExtensions.cs
@@ -0,0 +1,34 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Builder
+{
+    /// <summary>
+    /// Extension methods for adding terminal middleware.
+    /// </summary>
+    public static class RunExtensions
+    {
+        /// <summary>
+        /// Adds a terminal middleware delegate to the application's request pipeline.
+        /// </summary>
+        /// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
+        /// <param name="handler">A delegate that handles the request.</param>
+        public static void Run(this IApplicationBuilder app, RequestDelegate handler)
+        {
+            if (app == null)
+            {
+                throw new ArgumentNullException(nameof(app));
+            }
+
+            if (handler == null)
+            {
+                throw new ArgumentNullException(nameof(handler));
+            }
+
+            app.Use(_ => handler);
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/Extensions/UseExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/UseExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c0c9a0f6e50cc7cafcef36dfb0583167c85748e3
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Extensions/UseExtensions.cs
@@ -0,0 +1,33 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Builder
+{
+    /// <summary>
+    /// Extension methods for adding middleware.
+    /// </summary>
+    public static class UseExtensions
+    {
+        /// <summary>
+        /// Adds a middleware delegate defined in-line to the application's request pipeline.
+        /// </summary>
+        /// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
+        /// <param name="middleware">A function that handles the request or calls the given next function.</param>
+        /// <returns>The <see cref="IApplicationBuilder"/> instance.</returns>
+        public static IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware)
+        {
+            return app.Use(next =>
+            {
+                return context =>
+                {
+                    Func<Task> simpleNext = () => next(context);
+                    return middleware(context, simpleNext);
+                };
+            });
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c07fe1e9f1cf022a74ce88b33b97f070910b125e
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs
@@ -0,0 +1,224 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Abstractions;
+using Microsoft.Extensions.Internal;
+
+namespace Microsoft.AspNetCore.Builder
+{
+    /// <summary>
+    /// Extension methods for adding typed middleware.
+    /// </summary>
+    public static class UseMiddlewareExtensions
+    {
+        internal const string InvokeMethodName = "Invoke";
+        internal const string InvokeAsyncMethodName = "InvokeAsync";
+
+        private static readonly MethodInfo GetServiceInfo = typeof(UseMiddlewareExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static);
+
+        /// <summary>
+        /// Adds a middleware type to the application's request pipeline.
+        /// </summary>
+        /// <typeparam name="TMiddleware">The middleware type.</typeparam>
+        /// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
+        /// <param name="args">The arguments to pass to the middleware type instance's constructor.</param>
+        /// <returns>The <see cref="IApplicationBuilder"/> instance.</returns>
+        public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object[] args)
+        {
+            return app.UseMiddleware(typeof(TMiddleware), args);
+        }
+
+        /// <summary>
+        /// Adds a middleware type to the application's request pipeline.
+        /// </summary>
+        /// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
+        /// <param name="middleware">The middleware type.</param>
+        /// <param name="args">The arguments to pass to the middleware type instance's constructor.</param>
+        /// <returns>The <see cref="IApplicationBuilder"/> instance.</returns>
+        public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
+        {
+            if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo()))
+            {
+                // IMiddleware doesn't support passing args directly since it's
+                // activated from the container
+                if (args.Length > 0)
+                {
+                    throw new NotSupportedException(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)));
+                }
+
+                return UseMiddlewareInterface(app, middleware);
+            }
+
+            var applicationServices = app.ApplicationServices;
+            return app.Use(next =>
+            {
+                var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
+                var invokeMethods = methods.Where(m =>
+                    string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
+                    || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
+                    ).ToArray();
+
+                if (invokeMethods.Length > 1)
+                {
+                    throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
+                }
+
+                if (invokeMethods.Length == 0)
+                {
+                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
+                }
+
+                var methodinfo = invokeMethods[0];
+                if (!typeof(Task).IsAssignableFrom(methodinfo.ReturnType))
+                {
+                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
+                }
+
+                var parameters = methodinfo.GetParameters();
+                if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
+                {
+                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
+                }
+
+                var ctorArgs = new object[args.Length + 1];
+                ctorArgs[0] = next;
+                Array.Copy(args, 0, ctorArgs, 1, args.Length);
+                var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
+                if (parameters.Length == 1)
+                {
+                    return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
+                }
+
+                var factory = Compile<object>(methodinfo, parameters);
+
+                return context =>
+                {
+                    var serviceProvider = context.RequestServices ?? applicationServices;
+                    if (serviceProvider == null)
+                    {
+                        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
+                    }
+
+                    return factory(instance, context, serviceProvider);
+                };
+            });
+        }
+
+        private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, Type middlewareType)
+        {
+            return app.Use(next =>
+            {
+                return async context =>
+                {
+                    var middlewareFactory = (IMiddlewareFactory)context.RequestServices.GetService(typeof(IMiddlewareFactory));
+                    if (middlewareFactory == null)
+                    {
+                        // No middleware factory
+                        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)));
+                    }
+
+                    var middleware = middlewareFactory.Create(middlewareType);
+                    if (middleware == null)
+                    {
+                        // The factory returned null, it's a broken implementation
+                        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(), middlewareType));
+                    }
+
+                    try
+                    {
+                        await middleware.InvokeAsync(context, next);
+                    }
+                    finally
+                    {
+                        middlewareFactory.Release(middleware);
+                    }
+                };
+            });
+        }
+
+        private static Func<T, HttpContext, IServiceProvider, Task> Compile<T>(MethodInfo methodinfo, ParameterInfo[] parameters)
+        {
+            // If we call something like
+            //
+            // public class Middleware
+            // {
+            //    public Task Invoke(HttpContext context, ILoggerFactory loggeryFactory)
+            //    {
+            //
+            //    }
+            // }
+            //
+
+            // We'll end up with something like this:
+            //   Generic version:
+            //
+            //   Task Invoke(Middleware instance, HttpContext httpContext, IServiceprovider provider)
+            //   {
+            //      return instance.Invoke(httpContext, (ILoggerFactory)UseMiddlewareExtensions.GetService(provider, typeof(ILoggerFactory));
+            //   }
+
+            //   Non generic version:
+            //
+            //   Task Invoke(object instance, HttpContext httpContext, IServiceprovider provider)
+            //   {
+            //      return ((Middleware)instance).Invoke(httpContext, (ILoggerFactory)UseMiddlewareExtensions.GetService(provider, typeof(ILoggerFactory));
+            //   }
+
+            var middleware = typeof(T);
+
+            var httpContextArg = Expression.Parameter(typeof(HttpContext), "httpContext");
+            var providerArg = Expression.Parameter(typeof(IServiceProvider), "serviceProvider");
+            var instanceArg = Expression.Parameter(middleware, "middleware");
+
+            var methodArguments = new Expression[parameters.Length];
+            methodArguments[0] = httpContextArg;
+            for (int i = 1; i < parameters.Length; i++)
+            {
+                var parameterType = parameters[i].ParameterType;
+                if (parameterType.IsByRef)
+                {
+                    throw new NotSupportedException(Resources.FormatException_InvokeDoesNotSupportRefOrOutParams(InvokeMethodName));
+                }
+
+                var parameterTypeExpression = new Expression[]
+                {
+                    providerArg,
+                    Expression.Constant(parameterType, typeof(Type)),
+                    Expression.Constant(methodinfo.DeclaringType, typeof(Type))
+                };
+
+                var getServiceCall = Expression.Call(GetServiceInfo, parameterTypeExpression);
+                methodArguments[i] = Expression.Convert(getServiceCall, parameterType);
+            }
+
+            Expression middlewareInstanceArg = instanceArg;
+            if (methodinfo.DeclaringType != typeof(T))
+            {
+                middlewareInstanceArg = Expression.Convert(middlewareInstanceArg, methodinfo.DeclaringType);
+            }
+
+            var body = Expression.Call(middlewareInstanceArg, methodinfo, methodArguments);
+
+            var lambda = Expression.Lambda<Func<T, HttpContext, IServiceProvider, Task>>(body, instanceArg, httpContextArg, providerArg);
+
+            return lambda.Compile();
+        }
+
+        private static object GetService(IServiceProvider sp, Type type, Type middleware)
+        {
+            var service = sp.GetService(type);
+            if (service == null)
+            {
+                throw new InvalidOperationException(Resources.FormatException_InvokeMiddlewareNoService(type, middleware));
+            }
+
+            return service;
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/Extensions/UsePathBaseExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/UsePathBaseExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..482f2f481fe7a1e603aa36ee68e1148cb43e2742
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Extensions/UsePathBaseExtensions.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 Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Builder.Extensions;
+
+namespace Microsoft.AspNetCore.Builder
+{
+    /// <summary>
+    /// Extension methods for <see cref="IApplicationBuilder"/>.
+    /// </summary>
+    public static class UsePathBaseExtensions
+    {
+        /// <summary>
+        /// Adds a middleware that extracts the specified path base from request path and postpend it to the request path base.
+        /// </summary>
+        /// <param name="app">The <see cref="IApplicationBuilder"/> instance.</param>
+        /// <param name="pathBase">The path base to extract.</param>
+        /// <returns>The <see cref="IApplicationBuilder"/> instance.</returns>
+        public static IApplicationBuilder UsePathBase(this IApplicationBuilder app, PathString pathBase)
+        {
+            if (app == null)
+            {
+                throw new ArgumentNullException(nameof(app));
+            }
+
+            // Strip trailing slashes
+            pathBase = pathBase.Value?.TrimEnd('/');
+            if (!pathBase.HasValue)
+            {
+                return app;
+            }
+
+            return app.UseMiddleware<UsePathBaseMiddleware>(pathBase);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Abstractions/src/Extensions/UsePathBaseMiddleware.cs b/src/Http/Http.Abstractions/src/Extensions/UsePathBaseMiddleware.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6474aeda580c3d8a03bd4d2dffd4b01f67c5ea1f
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Extensions/UsePathBaseMiddleware.cs
@@ -0,0 +1,77 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Builder.Extensions
+{
+    /// <summary>
+    /// Represents a middleware that extracts the specified path base from request path and postpend it to the request path base.
+    /// </summary>
+    public class UsePathBaseMiddleware
+    {
+        private readonly RequestDelegate _next;
+        private readonly PathString _pathBase;
+
+        /// <summary>
+        /// Creates a new instace of <see cref="UsePathBaseMiddleware"/>.
+        /// </summary>
+        /// <param name="next">The delegate representing the next middleware in the request pipeline.</param>
+        /// <param name="pathBase">The path base to extract.</param>
+        public UsePathBaseMiddleware(RequestDelegate next, PathString pathBase)
+        {
+            if (next == null)
+            {
+                throw new ArgumentNullException(nameof(next));
+            }
+
+            if (!pathBase.HasValue)
+            {
+                throw new ArgumentException($"{nameof(pathBase)} cannot be null or empty.");
+            }
+
+            _next = next;
+            _pathBase = pathBase;
+        }
+
+        /// <summary>
+        /// Executes the middleware.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> for the current request.</param>
+        /// <returns>A task that represents the execution of this middleware.</returns>
+        public async Task Invoke(HttpContext context)
+        {
+            if (context == null)
+            {
+                throw new ArgumentNullException(nameof(context));
+            }
+
+            PathString matchedPath;
+            PathString remainingPath;
+
+            if (context.Request.Path.StartsWithSegments(_pathBase, out matchedPath, out remainingPath))
+            {
+                var originalPath = context.Request.Path;
+                var originalPathBase = context.Request.PathBase;
+                context.Request.Path = remainingPath;
+                context.Request.PathBase = originalPathBase.Add(matchedPath);
+
+                try
+                {
+                    await _next(context);
+                }
+                finally
+                {
+                    context.Request.Path = originalPath;
+                    context.Request.PathBase = originalPathBase;
+                }
+            }
+            else
+            {
+                await _next(context);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Abstractions/src/Extensions/UseWhenExtensions.cs b/src/Http/Http.Abstractions/src/Extensions/UseWhenExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f506c41c89412ef4e2ab829a3840d8daf1ee79b8
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Extensions/UseWhenExtensions.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 Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Builder
+{
+    using Predicate = Func<HttpContext, bool>;
+
+    /// <summary>
+    /// Extension methods for <see cref="IApplicationBuilder"/>.
+    /// </summary>
+    public static class UseWhenExtensions
+    {
+        /// <summary>
+        /// Conditionally creates a branch in the request pipeline that is rejoined to the main pipeline.
+        /// </summary>
+        /// <param name="app"></param>
+        /// <param name="predicate">Invoked with the request environment to determine if the branch should be taken</param>
+        /// <param name="configuration">Configures a branch to take</param>
+        /// <returns></returns>
+        public static IApplicationBuilder UseWhen(this IApplicationBuilder app, Predicate predicate, Action<IApplicationBuilder> configuration)
+        {
+            if (app == null)
+            {
+                throw new ArgumentNullException(nameof(app));
+            }
+
+            if (predicate == null)
+            {
+                throw new ArgumentNullException(nameof(predicate));
+            }
+
+            if (configuration == null)
+            {
+                throw new ArgumentNullException(nameof(configuration));
+            }
+
+            // Create and configure the branch builder right away; otherwise,
+            // we would end up running our branch after all the components
+            // that were subsequently added to the main builder.
+            var branchBuilder = app.New();
+            configuration(branchBuilder);
+
+            return app.Use(main =>
+            {
+                // This is called only when the main application builder 
+                // is built, not per request.
+                branchBuilder.Run(main);
+                var branch = branchBuilder.Build();
+
+                return context =>
+                {
+                    if (predicate(context))
+                    {
+                        return branch(context);
+                    }
+                    else
+                    {
+                        return main(context);
+                    }
+                };
+            });
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Abstractions/src/FragmentString.cs b/src/Http/Http.Abstractions/src/FragmentString.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c1cb30614960cc55cec1d5ff94d7e7719a601804
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/FragmentString.cs
@@ -0,0 +1,141 @@
+// 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
+{
+    /// <summary>
+    /// Provides correct handling for FragmentString value when needed to generate a URI string
+    /// </summary>
+    public struct FragmentString : IEquatable<FragmentString>
+    {
+        /// <summary>
+        /// Represents the empty fragment string. This field is read-only.
+        /// </summary>
+        public static readonly FragmentString Empty = new FragmentString(string.Empty);
+
+        private readonly string _value;
+
+        /// <summary>
+        /// Initialize the fragment string with a given value. This value must be in escaped and delimited format with
+        /// a leading '#' character.
+        /// </summary>
+        /// <param name="value">The fragment string to be assigned to the Value property.</param>
+        public FragmentString(string value)
+        {
+            if (!string.IsNullOrEmpty(value) && value[0] != '#')
+            {
+                throw new ArgumentException("The leading '#' must be included for a non-empty fragment.", nameof(value));
+            }
+            _value = value;
+        }
+
+        /// <summary>
+        /// The escaped fragment string with the leading '#' character
+        /// </summary>
+        public string Value
+        {
+            get { return _value; }
+        }
+
+        /// <summary>
+        /// True if the fragment string is not empty
+        /// </summary>
+        public bool HasValue
+        {
+            get { return !string.IsNullOrEmpty(_value); }
+        }
+
+        /// <summary>
+        /// Provides the fragment string escaped in a way which is correct for combining into the URI representation.
+        /// A leading '#' character will be included unless the Value is null or empty. Characters which are potentially
+        /// dangerous are escaped.
+        /// </summary>
+        /// <returns>The fragment string value</returns>
+        public override string ToString()
+        {
+            return ToUriComponent();
+        }
+
+        /// <summary>
+        /// Provides the fragment string escaped in a way which is correct for combining into the URI representation.
+        /// A leading '#' character will be included unless the Value is null or empty. Characters which are potentially
+        /// dangerous are escaped.
+        /// </summary>
+        /// <returns>The fragment string value</returns>
+        public string ToUriComponent()
+        {
+            // Escape things properly so System.Uri doesn't mis-interpret the data.
+            return HasValue ? _value : string.Empty;
+        }
+
+        /// <summary>
+        /// Returns an FragmentString given the fragment as it is escaped in the URI format. The string MUST NOT contain any
+        /// value that is not a fragment.
+        /// </summary>
+        /// <param name="uriComponent">The escaped fragment as it appears in the URI format.</param>
+        /// <returns>The resulting FragmentString</returns>
+        public static FragmentString FromUriComponent(string uriComponent)
+        {
+            if (String.IsNullOrEmpty(uriComponent))
+            {
+                return Empty;
+            }
+            return new FragmentString(uriComponent);
+        }
+
+        /// <summary>
+        /// Returns an FragmentString given the fragment as from a Uri object. Relative Uri objects are not supported.
+        /// </summary>
+        /// <param name="uri">The Uri object</param>
+        /// <returns>The resulting FragmentString</returns>
+        public static FragmentString FromUriComponent(Uri uri)
+        {
+            if (uri == null)
+            {
+                throw new ArgumentNullException(nameof(uri));
+            }
+
+            string fragmentValue = uri.GetComponents(UriComponents.Fragment, UriFormat.UriEscaped);
+            if (!string.IsNullOrEmpty(fragmentValue))
+            {
+                fragmentValue = "#" + fragmentValue;
+            }
+            return new FragmentString(fragmentValue);
+        }
+
+        public bool Equals(FragmentString other)
+        {
+            if (!HasValue && !other.HasValue)
+            {
+                return true;
+            }
+            return string.Equals(_value, other._value, StringComparison.Ordinal);
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(null, obj))
+            {
+                return !HasValue;
+            }
+            return obj is FragmentString && Equals((FragmentString)obj);
+        }
+
+        public override int GetHashCode()
+        {
+            return (HasValue ? _value.GetHashCode() : 0);
+        }
+
+        public static bool operator ==(FragmentString left, FragmentString right)
+        {
+            return left.Equals(right);
+        }
+
+        public static bool operator !=(FragmentString left, FragmentString right)
+        {
+            return !left.Equals(right);
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/HostString.cs b/src/Http/Http.Abstractions/src/HostString.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9496b26bacee1555097155cc5a2a2f0cf8aa0676
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/HostString.cs
@@ -0,0 +1,379 @@
+// 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.Globalization;
+using Microsoft.AspNetCore.Http.Abstractions;
+using Microsoft.AspNetCore.Http.Internal;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Represents the host portion of a URI can be used to construct URI's properly formatted and encoded for use in
+    /// HTTP headers.
+    /// </summary>
+    public struct HostString : IEquatable<HostString>
+    {
+        private readonly string _value;
+
+        /// <summary>
+        /// Creates a new HostString without modification. The value should be Unicode rather than punycode, and may have a port.
+        /// IPv4 and IPv6 addresses are also allowed, and also may have ports.
+        /// </summary>
+        /// <param name="value"></param>
+        public HostString(string value)
+        {
+            _value = value;
+        }
+
+        /// <summary>
+        /// Creates a new HostString from its host and port parts.
+        /// </summary>
+        /// <param name="host">The value should be Unicode rather than punycode. IPv6 addresses must use square braces.</param>
+        /// <param name="port">A positive, greater than 0 value representing the port in the host string.</param>
+        public HostString(string host, int port)
+        {
+            if(port <= 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(port), Resources.Exception_PortMustBeGreaterThanZero);
+            }
+
+            int index;
+            if (host.IndexOf('[') == -1
+                && (index = host.IndexOf(':')) >= 0
+                && index < host.Length - 1
+                && host.IndexOf(':', index + 1) >= 0)
+            {
+                // IPv6 without brackets ::1 is the only type of host with 2 or more colons
+                host =  $"[{host}]";
+            }
+
+            _value = host + ":" + port.ToString(CultureInfo.InvariantCulture);
+        }
+
+        /// <summary>
+        /// Returns the original value from the constructor.
+        /// </summary>
+        public string Value
+        {
+            get { return _value; }
+        }
+
+        public bool HasValue
+        {
+            get { return !string.IsNullOrEmpty(_value); }
+        }
+
+        /// <summary>
+        /// Returns the value of the host part of the value. The port is removed if it was present.
+        /// IPv6 addresses will have brackets added if they are missing.
+        /// </summary>
+        /// <returns></returns>
+        public string Host
+        {
+            get
+            {
+                GetParts(_value, out var host, out var port);
+
+                return host.ToString();
+            }
+        }
+
+        /// <summary>
+        /// Returns the value of the port part of the host, or <value>null</value> if none is found.
+        /// </summary>
+        /// <returns></returns>
+        public int? Port
+        {
+            get
+            {
+                GetParts(_value, out var host, out var port);
+
+                if (!StringSegment.IsNullOrEmpty(port)
+                    && int.TryParse(port.ToString(), NumberStyles.None, CultureInfo.InvariantCulture, out var p))
+                {
+                    return p;
+                }
+
+                return null;
+            }
+        }
+
+        /// <summary>
+        /// Returns the value as normalized by ToUriComponent().
+        /// </summary>
+        /// <returns></returns>
+        public override string ToString()
+        {
+            return ToUriComponent();
+        }
+
+        /// <summary>
+        /// Returns the value properly formatted and encoded for use in a URI in a HTTP header.
+        /// Any Unicode is converted to punycode. IPv6 addresses will have brackets added if they are missing.
+        /// </summary>
+        /// <returns></returns>
+        public string ToUriComponent()
+        {
+            if (string.IsNullOrEmpty(_value))
+            {
+                return string.Empty;
+            }
+
+            int i;
+            for (i = 0; i < _value.Length; ++i)
+            {
+                if (!HostStringHelper.IsSafeHostStringChar(_value[i]))
+                {
+                    break;
+                }
+            }
+
+            if (i != _value.Length)
+            {
+                GetParts(_value, out var host, out var port);
+
+                var mapping = new IdnMapping();
+                var encoded = mapping.GetAscii(host.Buffer, host.Offset, host.Length);
+
+                return StringSegment.IsNullOrEmpty(port)
+                    ? encoded
+                    : string.Concat(encoded, ":", port.ToString());
+            }
+
+            return _value;
+        }
+
+        /// <summary>
+        /// Creates a new HostString from the given URI component.
+        /// Any punycode will be converted to Unicode.
+        /// </summary>
+        /// <param name="uriComponent"></param>
+        /// <returns></returns>
+        public static HostString FromUriComponent(string uriComponent)
+        {
+            if (!string.IsNullOrEmpty(uriComponent))
+            {
+                int index;
+                if (uriComponent.IndexOf('[') >= 0)
+                {
+                    // IPv6 in brackets [::1], maybe with port
+                }
+                else if ((index = uriComponent.IndexOf(':')) >= 0
+                    && index < uriComponent.Length - 1
+                    && uriComponent.IndexOf(':', index + 1) >= 0)
+                {
+                    // IPv6 without brackets ::1 is the only type of host with 2 or more colons
+                }
+                else if (uriComponent.IndexOf("xn--", StringComparison.Ordinal) >= 0)
+                {
+                    // Contains punycode
+                    if (index >= 0)
+                    {
+                        // Has a port
+                        string port = uriComponent.Substring(index);
+                        var mapping = new IdnMapping();
+                        uriComponent = mapping.GetUnicode(uriComponent, 0, index) + port;
+                    }
+                    else
+                    {
+                        var mapping = new IdnMapping();
+                        uriComponent = mapping.GetUnicode(uriComponent);
+                    }
+                }
+            }
+            return new HostString(uriComponent);
+        }
+
+        /// <summary>
+        /// Creates a new HostString from the host and port of the give Uri instance.
+        /// Punycode will be converted to Unicode.
+        /// </summary>
+        /// <param name="uri"></param>
+        /// <returns></returns>
+        public static HostString FromUriComponent(Uri uri)
+        {
+            if (uri == null)
+            {
+                throw new ArgumentNullException(nameof(uri));
+            }
+
+            return new HostString(uri.GetComponents(
+                UriComponents.NormalizedHost | // Always convert punycode to Unicode.
+                UriComponents.HostAndPort, UriFormat.Unescaped));
+        }
+
+        /// <summary>
+        /// Matches the host portion of a host header value against a list of patterns.
+        /// The host may be the encoded punycode or decoded unicode form so long as the pattern
+        /// uses the same format.
+        /// </summary>
+        /// <param name="value">Host header value with or without a port.</param>
+        /// <param name="patterns">A set of pattern to match, without ports.</param>
+        /// <remarks>
+        /// The port on the given value is ignored. The patterns should not have ports.
+        /// The patterns may be exact matches like "example.com", a top level wildcard "*"
+        /// that matches all hosts, or a subdomain wildcard like "*.example.com" that matches
+        /// "abc.example.com:443" but not "example.com:443".
+        /// Matching is case insensitive.
+        /// </remarks>
+        /// <returns></returns>
+        public static bool MatchesAny(StringSegment value, IList<StringSegment> patterns)
+        {
+            if (value == null)
+            {
+                throw new ArgumentNullException(nameof(value));
+            }
+            if (patterns == null)
+            {
+                throw new ArgumentNullException(nameof(patterns));
+            }
+
+            // Drop the port
+            GetParts(value, out var host, out var port);
+
+            for (int i = 0; i < port.Length; i++)
+            {
+                if (port[i] < '0' || '9' < port[i])
+                {
+                    throw new FormatException($"The given host value '{value}' has a malformed port.");
+                }
+            }
+
+            for (int i = 0; i < patterns.Count; i++)
+            {
+                var pattern = patterns[i];
+
+                if (pattern == "*")
+                {
+                    return true;
+                }
+
+                if (StringSegment.Equals(pattern, host, StringComparison.OrdinalIgnoreCase))
+                {
+                    return true;
+                }
+
+                // Sub-domain wildcards: *.example.com
+                if (pattern.StartsWith("*.", StringComparison.Ordinal) && host.Length >= pattern.Length)
+                {
+                    // .example.com
+                    var allowedRoot = pattern.Subsegment(1);
+
+                    var hostRoot = host.Subsegment(host.Length - allowedRoot.Length);
+                    if (hostRoot.Equals(allowedRoot, StringComparison.OrdinalIgnoreCase))
+                    {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Compares the equality of the Value property, ignoring case.
+        /// </summary>
+        /// <param name="other"></param>
+        /// <returns></returns>
+        public bool Equals(HostString other)
+        {
+            if (!HasValue && !other.HasValue)
+            {
+                return true;
+            }
+            return string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase);
+        }
+
+        /// <summary>
+        /// Compares against the given object only if it is a HostString.
+        /// </summary>
+        /// <param name="obj"></param>
+        /// <returns></returns>
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(null, obj))
+            {
+                return !HasValue;
+            }
+            return obj is HostString && Equals((HostString)obj);
+        }
+
+        /// <summary>
+        /// Gets a hash code for the value.
+        /// </summary>
+        /// <returns></returns>
+        public override int GetHashCode()
+        {
+            return (HasValue ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0);
+        }
+
+        /// <summary>
+        /// Compares the two instances for equality.
+        /// </summary>
+        /// <param name="left"></param>
+        /// <param name="right"></param>
+        /// <returns></returns>
+        public static bool operator ==(HostString left, HostString right)
+        {
+            return left.Equals(right);
+        }
+
+        /// <summary>
+        /// Compares the two instances for inequality.
+        /// </summary>
+        /// <param name="left"></param>
+        /// <param name="right"></param>
+        /// <returns></returns>
+        public static bool operator !=(HostString left, HostString right)
+        {
+            return !left.Equals(right);
+        }
+
+        /// <summary>
+        /// Parses the current value. IPv6 addresses will have brackets added if they are missing.
+        /// </summary>
+        private static void GetParts(StringSegment value, out StringSegment host, out StringSegment port)
+        {
+            int index;
+            port = null;
+            host = null;
+
+            if (StringSegment.IsNullOrEmpty(value))
+            {
+                return;
+            }
+            else if ((index = value.IndexOf(']')) >= 0)
+            {
+                // IPv6 in brackets [::1], maybe with port
+                host = value.Subsegment(0, index + 1);
+                // Is there a colon and at least one character?
+                if (index + 2 < value.Length && value[index + 1] == ':')
+                {
+                    port = value.Subsegment(index + 2);
+                }
+            }
+            else if ((index = value.IndexOf(':')) >= 0
+                && index < value.Length - 1
+                && value.IndexOf(':', index + 1) >= 0)
+            {
+                // IPv6 without brackets ::1 is the only type of host with 2 or more colons
+                host = $"[{value}]";
+                port = null;
+            }
+            else if (index >= 0)
+            {
+                // Has a port
+                host = value.Subsegment(0, index);
+                port = value.Subsegment(index + 1);
+            }
+            else
+            {
+                host = value;
+                port = null;
+            }
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/HttpContext.cs b/src/Http/Http.Abstractions/src/HttpContext.cs
new file mode 100644
index 0000000000000000000000000000000000000000..60c938db0a13a714958b3175b3215cae3639f754
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/HttpContext.cs
@@ -0,0 +1,87 @@
+// 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.Security.Claims;
+using System.Threading;
+using Microsoft.AspNetCore.Http.Authentication;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Encapsulates all HTTP-specific information about an individual HTTP request.
+    /// </summary>
+    public abstract class HttpContext
+    {
+        /// <summary>
+        /// Gets the collection of HTTP features provided by the server and middleware available on this request.
+        /// </summary>
+        public abstract IFeatureCollection Features { get; }
+
+        /// <summary>
+        /// Gets the <see cref="HttpRequest"/> object for this request.
+        /// </summary>
+        public abstract HttpRequest Request { get; }
+
+        /// <summary>
+        /// Gets the <see cref="HttpResponse"/> object for this request.
+        /// </summary>
+        public abstract HttpResponse Response { get; }
+
+        /// <summary>
+        /// Gets information about the underlying connection for this request.
+        /// </summary>
+        public abstract ConnectionInfo Connection { get; }
+
+        /// <summary>
+        /// Gets an object that manages the establishment of WebSocket connections for this request.
+        /// </summary>
+        public abstract WebSocketManager WebSockets { get; }
+
+        /// <summary>
+        /// This is obsolete and will be removed in a future version. 
+        /// The recommended alternative is to use Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions.
+        /// See https://go.microsoft.com/fwlink/?linkid=845470.
+        /// </summary>
+        [Obsolete("This is obsolete and will be removed in a future version. The recommended alternative is to use Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions. See https://go.microsoft.com/fwlink/?linkid=845470.")]
+        public abstract AuthenticationManager Authentication { get; }
+
+        /// <summary>
+        /// Gets or sets the user for this request.
+        /// </summary>
+        public abstract ClaimsPrincipal User { get; set; }
+
+        /// <summary>
+        /// Gets or sets a key/value collection that can be used to share data within the scope of this request.
+        /// </summary>
+        public abstract IDictionary<object, object> Items { get; set; }
+
+        /// <summary>
+        /// Gets or sets the <see cref="IServiceProvider"/> that provides access to the request's service container.
+        /// </summary>
+        public abstract IServiceProvider RequestServices { get; set; }
+
+        /// <summary>
+        /// Notifies when the connection underlying this request is aborted and thus request operations should be
+        /// cancelled.
+        /// </summary>
+        public abstract CancellationToken RequestAborted { get; set; }
+
+        /// <summary>
+        /// Gets or sets a unique identifier to represent this request in trace logs.
+        /// </summary>
+        public abstract string TraceIdentifier { get; set; }
+
+        /// <summary>
+        /// Gets or sets the object used to manage user session data for this request.
+        /// </summary>
+        public abstract ISession Session { get; set; }
+
+        /// <summary>
+        /// Aborts the connection underlying this request.
+        /// </summary>
+        public abstract void Abort();
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/HttpMethods.cs b/src/Http/Http.Abstractions/src/HttpMethods.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1ccee896e7020abe2bd65a2f8e9f0b55661af516
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/HttpMethods.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;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public static class HttpMethods
+    {
+        public static readonly string Connect = "CONNECT";
+        public static readonly string Delete = "DELETE";
+        public static readonly string Get = "GET";
+        public static readonly string Head = "HEAD";
+        public static readonly string Options = "OPTIONS";
+        public static readonly string Patch = "PATCH";
+        public static readonly string Post = "POST";
+        public static readonly string Put = "PUT";
+        public static readonly string Trace = "TRACE";
+
+        public static bool IsConnect(string method)
+        {
+            return object.ReferenceEquals(Connect, method) || StringComparer.OrdinalIgnoreCase.Equals(Connect, method);
+        }
+
+        public static bool IsDelete(string method)
+        {
+            return object.ReferenceEquals(Delete, method) || StringComparer.OrdinalIgnoreCase.Equals(Delete, method);
+        }
+
+        public static bool IsGet(string method)
+        {
+            return object.ReferenceEquals(Get, method) || StringComparer.OrdinalIgnoreCase.Equals(Get, method);
+        }
+
+        public static bool IsHead(string method)
+        {
+            return object.ReferenceEquals(Head, method) || StringComparer.OrdinalIgnoreCase.Equals(Head, method);
+        }
+
+        public static bool IsOptions(string method)
+        {
+            return object.ReferenceEquals(Options, method) || StringComparer.OrdinalIgnoreCase.Equals(Options, method);
+        }
+
+        public static bool IsPatch(string method)
+        {
+            return object.ReferenceEquals(Patch, method) || StringComparer.OrdinalIgnoreCase.Equals(Patch, method);
+        }
+
+        public static bool IsPost(string method)
+        {
+            return object.ReferenceEquals(Post, method) || StringComparer.OrdinalIgnoreCase.Equals(Post, method);
+        }
+
+        public static bool IsPut(string method)
+        {
+            return object.ReferenceEquals(Put, method) || StringComparer.OrdinalIgnoreCase.Equals(Put, method);
+        }
+
+        public static bool IsTrace(string method)
+        {
+            return object.ReferenceEquals(Trace, method) || StringComparer.OrdinalIgnoreCase.Equals(Trace, method);
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/HttpRequest.cs b/src/Http/Http.Abstractions/src/HttpRequest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a4337b77664c554a60bb42b237a8f1d1c36e6882
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/HttpRequest.cs
@@ -0,0 +1,121 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Represents the incoming side of an individual HTTP request.
+    /// </summary>
+    public abstract class HttpRequest
+    {
+        /// <summary>
+        /// Gets the <see cref="HttpContext"/> for this request.
+        /// </summary>
+        public abstract HttpContext HttpContext { get; }
+
+        /// <summary>
+        /// Gets or sets the HTTP method.
+        /// </summary>
+        /// <returns>The HTTP method.</returns>
+        public abstract string Method { get; set; }
+
+        /// <summary>
+        /// Gets or sets the HTTP request scheme.
+        /// </summary>
+        /// <returns>The HTTP request scheme.</returns>
+        public abstract string Scheme { get; set; }
+
+        /// <summary>
+        /// Returns true if the RequestScheme is https.
+        /// </summary>
+        /// <returns>true if this request is using https; otherwise, false.</returns>
+        public abstract bool IsHttps { get; set; }
+
+        /// <summary>
+        /// Gets or sets the Host header. May include the port.
+        /// </summary>
+        /// <return>The Host header.</return>
+        public abstract HostString Host { get; set; }
+
+        /// <summary>
+        /// Gets or sets the RequestPathBase.
+        /// </summary>
+        /// <returns>The RequestPathBase.</returns>
+        public abstract PathString PathBase { get; set; }
+
+        /// <summary>
+        /// Gets or sets the request path from RequestPath.
+        /// </summary>
+        /// <returns>The request path from RequestPath.</returns>
+        public abstract PathString Path { get; set; }
+
+        /// <summary>
+        /// Gets or sets the raw query string used to create the query collection in Request.Query.
+        /// </summary>
+        /// <returns>The raw query string.</returns>
+        public abstract QueryString QueryString { get; set; }
+
+        /// <summary>
+        /// Gets the query value collection parsed from Request.QueryString.
+        /// </summary>
+        /// <returns>The query value collection parsed from Request.QueryString.</returns>
+        public abstract IQueryCollection Query { get; set; }
+
+        /// <summary>
+        /// Gets or sets the RequestProtocol.
+        /// </summary>
+        /// <returns>The RequestProtocol.</returns>
+        public abstract string Protocol { get; set; }
+
+        /// <summary>
+        /// Gets the request headers.
+        /// </summary>
+        /// <returns>The request headers.</returns>
+        public abstract IHeaderDictionary Headers { get; }
+
+        /// <summary>
+        /// Gets the collection of Cookies for this request.
+        /// </summary>
+        /// <returns>The collection of Cookies for this request.</returns>
+        public abstract IRequestCookieCollection Cookies { get; set; }
+
+        /// <summary>
+        /// Gets or sets the Content-Length header.
+        /// </summary>
+        /// <returns>The value of the Content-Length header, if any.</returns>
+        public abstract long? ContentLength { get; set; }
+
+        /// <summary>
+        /// Gets or sets the Content-Type header.
+        /// </summary>
+        /// <returns>The Content-Type header.</returns>
+        public abstract string ContentType { get; set; }
+
+        /// <summary>
+        /// Gets or sets the RequestBody Stream.
+        /// </summary>
+        /// <returns>The RequestBody Stream.</returns>
+        public abstract Stream Body { get; set; }
+
+        /// <summary>
+        /// Checks the Content-Type header for form types.
+        /// </summary>
+        /// <returns>true if the Content-Type header represents a form content type; otherwise, false.</returns>
+        public abstract bool HasFormContentType { get; }
+
+        /// <summary>
+        /// Gets or sets the request body as a form.
+        /// </summary>
+        public abstract IFormCollection Form { get; set; }
+
+        /// <summary>
+        /// Reads the request body if it is a form.
+        /// </summary>
+        /// <returns></returns>
+        public abstract Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken = new CancellationToken());
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/HttpResponse.cs b/src/Http/Http.Abstractions/src/HttpResponse.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8a1e5d49082904198c0e36f4df36ddeeb4b8425f
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/HttpResponse.cs
@@ -0,0 +1,109 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Represents the outgoing side of an individual HTTP request.
+    /// </summary>
+    public abstract class HttpResponse
+    {
+        private static readonly Func<object, Task> _callbackDelegate = callback => ((Func<Task>)callback)();
+        private static readonly Func<object, Task> _disposeDelegate = disposable =>
+        {
+            ((IDisposable)disposable).Dispose();
+            return Task.CompletedTask;
+        };
+
+        /// <summary>
+        /// Gets the <see cref="HttpContext"/> for this response.
+        /// </summary>
+        public abstract HttpContext HttpContext { get; }
+
+        /// <summary>
+        /// Gets or sets the HTTP response code.
+        /// </summary>
+        public abstract int StatusCode { get; set; }
+
+        /// <summary>
+        /// Gets the response headers.
+        /// </summary>
+        public abstract IHeaderDictionary Headers { get; }
+
+        /// <summary>
+        /// Gets or sets the response body <see cref="Stream"/>.
+        /// </summary>
+        public abstract Stream Body { get; set; }
+
+        /// <summary>
+        /// Gets or sets the value for the <c>Content-Length</c> response header.
+        /// </summary>
+        public abstract long? ContentLength { get; set; }
+
+        /// <summary>
+        /// Gets or sets the value for the <c>Content-Type</c> response header.
+        /// </summary>
+        public abstract string ContentType { get; set; }
+
+        /// <summary>
+        /// Gets an object that can be used to manage cookies for this response.
+        /// </summary>
+        public abstract IResponseCookies Cookies { get; }
+
+        /// <summary>
+        /// Gets a value indicating whether response headers have been sent to the client.
+        /// </summary>
+        public abstract bool HasStarted { get; }
+
+        /// <summary>
+        /// Adds a delegate to be invoked just before response headers will be sent to the client.
+        /// </summary>
+        /// <param name="callback">The delegate to execute.</param>
+        /// <param name="state">A state object to capture and pass back to the delegate.</param>
+        public abstract void OnStarting(Func<object, Task> callback, object state);
+
+        /// <summary>
+        /// Adds a delegate to be invoked just before response headers will be sent to the client.
+        /// </summary>
+        /// <param name="callback">The delegate to execute.</param>
+        public virtual void OnStarting(Func<Task> callback) => OnStarting(_callbackDelegate, callback);
+
+        /// <summary>
+        /// Adds a delegate to be invoked after the response has finished being sent to the client.
+        /// </summary>
+        /// <param name="callback">The delegate to invoke.</param>
+        /// <param name="state">A state object to capture and pass back to the delegate.</param>
+        public abstract void OnCompleted(Func<object, Task> callback, object state);
+
+        /// <summary>
+        /// Registers an object for disposal by the host once the request has finished processing.
+        /// </summary>
+        /// <param name="disposable">The object to be disposed.</param>
+        public virtual void RegisterForDispose(IDisposable disposable) => OnCompleted(_disposeDelegate, disposable);
+
+        /// <summary>
+        /// Adds a delegate to be invoked after the response has finished being sent to the client.
+        /// </summary>
+        /// <param name="callback">The delegate to invoke.</param>
+        public virtual void OnCompleted(Func<Task> callback) => OnCompleted(_callbackDelegate, callback);
+
+        /// <summary>
+        /// Returns a temporary redirect response (HTTP 302) to the client.
+        /// </summary>
+        /// <param name="location">The URL to redirect the client to. This must be properly encoded for use in http headers
+        /// where only ASCII characters are allowed.</param>
+        public virtual void Redirect(string location) => Redirect(location, permanent: false);
+
+        /// <summary>
+        /// Returns a redirect response (HTTP 301 or HTTP 302) to the client.
+        /// </summary>
+        /// <param name="location">The URL to redirect the client to. This must be properly encoded for use in http headers
+        /// where only ASCII characters are allowed.</param>
+        /// <param name="permanent"><c>True</c> if the redirect is permanent (301), otherwise <c>false</c> (302).</param>
+        public abstract void Redirect(string location, bool permanent);
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/IApplicationBuilder.cs b/src/Http/Http.Abstractions/src/IApplicationBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6110d7f3db6e13327b93ebb0a550c30753ec3765
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/IApplicationBuilder.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;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Builder
+{
+    /// <summary>
+    /// Defines a class that provides the mechanisms to configure an application's request pipeline.
+    /// </summary>
+    public interface IApplicationBuilder
+    {
+        /// <summary>
+        /// Gets or sets the <see cref="IServiceProvider"/> that provides access to the application's service container.
+        /// </summary>
+        IServiceProvider ApplicationServices { get; set; }
+
+        /// <summary>
+        /// Gets the set of HTTP features the application's server provides.
+        /// </summary>
+        IFeatureCollection ServerFeatures { get; }
+
+        /// <summary>
+        /// Gets a key/value collection that can be used to share data between middleware.
+        /// </summary>
+        IDictionary<string, object> Properties { get; }
+
+        /// <summary>
+        /// Adds a middleware delegate to the application's request pipeline.
+        /// </summary>
+        /// <param name="middleware">The middleware delegate.</param>
+        /// <returns>The <see cref="IApplicationBuilder"/>.</returns>
+        IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
+
+        /// <summary>
+        /// Creates a new <see cref="IApplicationBuilder"/> that shares the <see cref="Properties"/> of this
+        /// <see cref="IApplicationBuilder"/>.
+        /// </summary>
+        /// <returns>The new <see cref="IApplicationBuilder"/>.</returns>
+        IApplicationBuilder New();
+
+        /// <summary>
+        /// Builds the delegate used by this application to process HTTP requests.
+        /// </summary>
+        /// <returns>The request handling delegate.</returns>
+        RequestDelegate Build();
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/IHttpContextAccessor.cs b/src/Http/Http.Abstractions/src/IHttpContextAccessor.cs
new file mode 100644
index 0000000000000000000000000000000000000000..dc8ec34a7c149b6172cfe152d1b015450c8ce6f5
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/IHttpContextAccessor.cs
@@ -0,0 +1,10 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http
+{
+    public interface IHttpContextAccessor
+    {
+        HttpContext HttpContext { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Abstractions/src/IHttpContextFactory.cs b/src/Http/Http.Abstractions/src/IHttpContextFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7d049626c39aad299767662e6f61045fa1e30f12
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/IHttpContextFactory.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.
+
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public interface IHttpContextFactory
+    {
+        HttpContext Create(IFeatureCollection featureCollection);
+        void Dispose(HttpContext httpContext);
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/IMiddleware.cs b/src/Http/Http.Abstractions/src/IMiddleware.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f92527f3f5e103ad14f1b4ec6400e2fafcc2a9a4
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/IMiddleware.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.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Defines middleware that can be added to the application's request pipeline.
+    /// </summary>
+    public interface IMiddleware
+    {
+        /// <summary>
+        /// Request handling method.
+        /// </summary>
+        /// <param name="context">The <see cref="HttpContext"/> for the current request.</param>
+        /// <param name="next">The delegate representing the remaining middleware in the request pipeline.</param>
+        /// <returns>A <see cref="Task"/> that represents the execution of this middleware.</returns>
+        Task InvokeAsync(HttpContext context, RequestDelegate next);
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/IMiddlewareFactory.cs b/src/Http/Http.Abstractions/src/IMiddlewareFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5d9fda8a751ad3a8ff4cc85281bb7cb3bbfadf19
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/IMiddlewareFactory.cs
@@ -0,0 +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 System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Provides methods to create middleware.
+    /// </summary>
+    public interface IMiddlewareFactory
+    {
+        /// <summary>
+        /// Creates a middleware instance for each request.
+        /// </summary>
+        /// <param name="middlewareType">The concrete <see cref="Type"/> of the <see cref="IMiddleware"/>.</param>
+        /// <returns>The <see cref="IMiddleware"/> instance.</returns>
+        IMiddleware Create(Type middlewareType);
+
+        /// <summary>
+        /// Releases a <see cref="IMiddleware"/> instance at the end of each request.
+        /// </summary>
+        /// <param name="middleware">The <see cref="IMiddleware"/> instance to release.</param>
+        void Release(IMiddleware middleware);
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/Internal/HeaderSegment.cs b/src/Http/Http.Abstractions/src/Internal/HeaderSegment.cs
new file mode 100644
index 0000000000000000000000000000000000000000..eed9d80f88356f0b4f7468896f97c1fda27bd9dc
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Internal/HeaderSegment.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 Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    public struct HeaderSegment : IEquatable<HeaderSegment>
+    {
+        private readonly StringSegment _formatting;
+        private readonly StringSegment _data;
+
+        // <summary>
+        // Initializes a new instance of the <see cref="HeaderSegment"/> structure.
+        // </summary>
+        public HeaderSegment(StringSegment formatting, StringSegment data)
+        {
+            _formatting = formatting;
+            _data = data;
+        }
+
+        public StringSegment Formatting
+        {
+            get { return _formatting; }
+        }
+
+        public StringSegment Data
+        {
+            get { return _data; }
+        }
+
+        public bool Equals(HeaderSegment other)
+        {
+            return _formatting.Equals(other._formatting) && _data.Equals(other._data);
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(null, obj))
+            {
+                return false;
+            }
+
+            return obj is HeaderSegment && Equals((HeaderSegment)obj);
+        }
+
+        public override int GetHashCode()
+        {
+            unchecked
+            {
+                return (_formatting.GetHashCode() * 397) ^ _data.GetHashCode();
+            }
+        }
+
+        public static bool operator ==(HeaderSegment left, HeaderSegment right)
+        {
+            return left.Equals(right);
+        }
+
+        public static bool operator !=(HeaderSegment left, HeaderSegment right)
+        {
+            return !left.Equals(right);
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/Internal/HeaderSegmentCollection.cs b/src/Http/Http.Abstractions/src/Internal/HeaderSegmentCollection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..40c40a8eb349193f59fb49d328cf375b3cb06cd6
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Internal/HeaderSegmentCollection.cs
@@ -0,0 +1,297 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    public struct HeaderSegmentCollection : IEnumerable<HeaderSegment>, IEquatable<HeaderSegmentCollection>
+    {
+        private readonly StringValues _headers;
+
+        public HeaderSegmentCollection(StringValues headers)
+        {
+            _headers = headers;
+        }
+
+        public bool Equals(HeaderSegmentCollection other)
+        {
+            return StringValues.Equals(_headers, other._headers);
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(null, obj))
+            {
+                return false;
+            }
+
+            return obj is HeaderSegmentCollection && Equals((HeaderSegmentCollection)obj);
+        }
+
+        public override int GetHashCode()
+        {
+            return (!StringValues.IsNullOrEmpty(_headers) ? _headers.GetHashCode() : 0);
+        }
+
+        public static bool operator ==(HeaderSegmentCollection left, HeaderSegmentCollection right)
+        {
+            return left.Equals(right);
+        }
+
+        public static bool operator !=(HeaderSegmentCollection left, HeaderSegmentCollection right)
+        {
+            return !left.Equals(right);
+        }
+
+        public Enumerator GetEnumerator()
+        {
+            return new Enumerator(_headers);
+        }
+
+        IEnumerator<HeaderSegment> IEnumerable<HeaderSegment>.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        public struct Enumerator : IEnumerator<HeaderSegment>
+        {
+            private readonly StringValues _headers;
+            private int _index;
+
+            private string _header;
+            private int _headerLength;
+            private int _offset;
+
+            private int _leadingStart;
+            private int _leadingEnd;
+            private int _valueStart;
+            private int _valueEnd;
+            private int _trailingStart;
+
+            private Mode _mode;
+
+            public Enumerator(StringValues headers)
+            {
+                _headers = headers;
+                _header = string.Empty;
+                _headerLength = -1;
+                _index = -1;
+                _offset = -1;
+                _leadingStart = -1;
+                _leadingEnd = -1;
+                _valueStart = -1;
+                _valueEnd = -1;
+                _trailingStart = -1;
+                _mode = Mode.Leading;
+            }
+
+            private enum Mode
+            {
+                Leading,
+                Value,
+                ValueQuoted,
+                Trailing,
+                Produce,
+            }
+
+            private enum Attr
+            {
+                Value,
+                Quote,
+                Delimiter,
+                Whitespace
+            }
+
+            public HeaderSegment Current
+            {
+                get
+                {
+                    return new HeaderSegment(
+                        new StringSegment(_header, _leadingStart, _leadingEnd - _leadingStart),
+                        new StringSegment(_header, _valueStart, _valueEnd - _valueStart));
+                }
+            }
+
+            object IEnumerator.Current
+            {
+                get { return Current; }
+            }
+
+            public void Dispose()
+            {
+            }
+
+            public bool MoveNext()
+            {
+                while (true)
+                {
+                    if (_mode == Mode.Produce)
+                    {
+                        _leadingStart = _trailingStart;
+                        _leadingEnd = -1;
+                        _valueStart = -1;
+                        _valueEnd = -1;
+                        _trailingStart = -1;
+
+                        if (_offset == _headerLength &&
+                            _leadingStart != -1 &&
+                            _leadingStart != _offset)
+                        {
+                            // Also produce trailing whitespace
+                            _leadingEnd = _offset;
+                            return true;
+                        }
+                        _mode = Mode.Leading;
+                    }
+
+                    // if end of a string
+                    if (_offset == _headerLength)
+                    {
+                        ++_index;
+                        _offset = -1;
+                        _leadingStart = 0;
+                        _leadingEnd = -1;
+                        _valueStart = -1;
+                        _valueEnd = -1;
+                        _trailingStart = -1;
+
+                        // if that was the last string
+                        if (_index == _headers.Count)
+                        {
+                            // no more move nexts
+                            return false;
+                        }
+
+                        // grab the next string
+                        _header = _headers[_index] ?? string.Empty;
+                        _headerLength = _header.Length;
+                    }
+                    while (true)
+                    {
+                        ++_offset;
+                        char ch = _offset == _headerLength ? (char)0 : _header[_offset];
+                        // todo - array of attrs
+                        Attr attr = char.IsWhiteSpace(ch) ? Attr.Whitespace : ch == '\"' ? Attr.Quote : (ch == ',' || ch == (char)0) ? Attr.Delimiter : Attr.Value;
+
+                        switch (_mode)
+                        {
+                            case Mode.Leading:
+                                switch (attr)
+                                {
+                                    case Attr.Delimiter:
+                                        _valueStart = _valueStart == -1 ? _offset : _valueStart;
+                                        _valueEnd = _valueEnd == -1 ? _offset : _valueEnd;
+                                        _trailingStart = _trailingStart == -1 ? _offset : _trailingStart;
+                                        _leadingEnd = _offset;
+                                        _mode = Mode.Produce;
+                                        break;
+                                    case Attr.Quote:
+                                        _leadingEnd = _offset;
+                                        _valueStart = _offset;
+                                        _mode = Mode.ValueQuoted;
+                                        break;
+                                    case Attr.Value:
+                                        _leadingEnd = _offset;
+                                        _valueStart = _offset;
+                                        _mode = Mode.Value;
+                                        break;
+                                    case Attr.Whitespace:
+                                        // more
+                                        break;
+                                }
+                                break;
+                            case Mode.Value:
+                                switch (attr)
+                                {
+                                    case Attr.Quote:
+                                        _mode = Mode.ValueQuoted;
+                                        break;
+                                    case Attr.Delimiter:
+                                        _valueEnd = _offset;
+                                        _trailingStart = _offset;
+                                        _mode = Mode.Produce;
+                                        break;
+                                    case Attr.Value:
+                                        // more
+                                        break;
+                                    case Attr.Whitespace:
+                                        _valueEnd = _offset;
+                                        _trailingStart = _offset;
+                                        _mode = Mode.Trailing;
+                                        break;
+                                }
+                                break;
+                            case Mode.ValueQuoted:
+                                switch (attr)
+                                {
+                                    case Attr.Quote:
+                                        _mode = Mode.Value;
+                                        break;
+                                    case Attr.Delimiter:
+                                        if (ch == (char)0)
+                                        {
+                                            _valueEnd = _offset;
+                                            _trailingStart = _offset;
+                                            _mode = Mode.Produce;
+                                        }
+                                        break;
+                                    case Attr.Value:
+                                    case Attr.Whitespace:
+                                        // more
+                                        break;
+                                }
+                                break;
+                            case Mode.Trailing:
+                                switch (attr)
+                                {
+                                    case Attr.Delimiter:
+                                        _mode = Mode.Produce;
+                                        break;
+                                    case Attr.Quote:
+                                        // back into value
+                                        _trailingStart = -1;
+                                        _valueEnd = -1;
+                                        _mode = Mode.ValueQuoted;
+                                        break;
+                                    case Attr.Value:
+                                        // back into value
+                                        _trailingStart = -1;
+                                        _valueEnd = -1;
+                                        _mode = Mode.Value;
+                                        break;
+                                    case Attr.Whitespace:
+                                        // more
+                                        break;
+                                }
+                                break;
+                        }
+                        if (_mode == Mode.Produce)
+                        {
+                            return true;
+                        }
+                    }
+                }
+            }
+
+            public void Reset()
+            {
+                _index = 0;
+                _offset = 0;
+                _leadingStart = 0;
+                _leadingEnd = 0;
+                _valueStart = 0;
+                _valueEnd = 0;
+            }
+        }
+    }
+
+}
diff --git a/src/Http/Http.Abstractions/src/Internal/HostStringHelper.cs b/src/Http/Http.Abstractions/src/Internal/HostStringHelper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f4cfac52afb3c0c91ced09ccceef0ce450e343ff
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Internal/HostStringHelper.cs
@@ -0,0 +1,36 @@
+// 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.Internal
+{
+    internal class HostStringHelper
+    {
+        // Allowed Characters:
+        // A-Z, a-z, 0-9, ., 
+        // -, %, [, ], : 
+        // Above for IPV6
+        private static bool[] SafeHostStringChars = {
+            false, false, false, false, false, false, false, false,     // 0x00 - 0x07
+            false, false, false, false, false, false, false, false,     // 0x08 - 0x0F
+            false, false, false, false, false, false, false, false,     // 0x10 - 0x17
+            false, false, false, false, false, false, false, false,     // 0x18 - 0x1F
+            false, false, false, false, false, true,  false, false,     // 0x20 - 0x27
+            false, false, false, false, false, true,  true,  false,     // 0x28 - 0x2F
+            true,  true,  true,  true,  true,  true,  true,  true,      // 0x30 - 0x37
+            true,  true,  true,  false, false, false, false, false,     // 0x38 - 0x3F
+            false, true,  true,  true,  true,  true,  true,  true,      // 0x40 - 0x47
+            true,  true,  true,  true,  true,  true,  true,  true,      // 0x48 - 0x4F
+            true,  true,  true,  true,  true,  true,  true,  true,      // 0x50 - 0x57
+            true,  true,  true,  true,  false, true,  false, false,     // 0x58 - 0x5F
+            false, true,  true,  true,  true,  true,  true,  true,      // 0x60 - 0x67
+            true,  true,  true,  true,  true,  true,  true,  true,      // 0x68 - 0x6F
+            true,  true,  true,  true,  true,  true,  true,  true,      // 0x70 - 0x77
+            true,  true,  true,  false, false, false, false, false,     // 0x78 - 0x7F
+        };
+
+        public static bool IsSafeHostStringChar(char c)
+        {
+            return c < SafeHostStringChars.Length && SafeHostStringChars[c];
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/Internal/ParsingHelpers.cs b/src/Http/Http.Abstractions/src/Internal/ParsingHelpers.cs
new file mode 100644
index 0000000000000000000000000000000000000000..185fc40ac7c76b01397007de698f4b74a3790c90
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Internal/ParsingHelpers.cs
@@ -0,0 +1,165 @@
+// 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 Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    public static class ParsingHelpers
+    {
+        public static StringValues GetHeader(IHeaderDictionary headers, string key)
+        {
+            StringValues value;
+            return headers.TryGetValue(key, out value) ? value : StringValues.Empty;
+        }
+
+        public static StringValues GetHeaderSplit(IHeaderDictionary headers, string key)
+        {
+            var values = GetHeaderUnmodified(headers, key);
+            return new StringValues(GetHeaderSplitImplementation(values).ToArray());
+        }
+
+        private static IEnumerable<string> GetHeaderSplitImplementation(StringValues values)
+        {
+            foreach (var segment in new HeaderSegmentCollection(values))
+            {
+                if (!StringSegment.IsNullOrEmpty(segment.Data))
+                {
+                    var value = DeQuote(segment.Data.Value);
+                    if (!string.IsNullOrEmpty(value))
+                    {
+                        yield return value;
+                    }
+                }
+            }
+        }
+
+        public static StringValues GetHeaderUnmodified(IHeaderDictionary headers, string key)
+        {
+            if (headers == null)
+            {
+                throw new ArgumentNullException(nameof(headers));
+            }
+
+            StringValues values;
+            return headers.TryGetValue(key, out values) ? values : StringValues.Empty;
+        }
+
+        public static void SetHeaderJoined(IHeaderDictionary headers, string key, StringValues value)
+        {
+            if (headers == null)
+            {
+                throw new ArgumentNullException(nameof(headers));
+            }
+
+            if (string.IsNullOrEmpty(key))
+            {
+                throw new ArgumentNullException(nameof(key));
+            }
+            if (StringValues.IsNullOrEmpty(value))
+            {
+                headers.Remove(key);
+            }
+            else
+            {
+                headers[key] = string.Join(",", value.Select((s) => QuoteIfNeeded(s)));
+            }
+        }
+
+        // Quote items that contain commas and are not already quoted.
+        private static string QuoteIfNeeded(string value)
+        {
+            if (!string.IsNullOrEmpty(value) &&
+                value.Contains(',') &&
+                (value[0] != '"' || value[value.Length - 1] != '"'))
+            {
+                return $"\"{value}\"";
+            }
+            return value;
+        }
+
+        private static string DeQuote(string value)
+        {
+            if (!string.IsNullOrEmpty(value) &&
+                (value.Length > 1 && value[0] == '"' && value[value.Length - 1] == '"'))
+            {
+                value = value.Substring(1, value.Length - 2);
+            }
+
+            return value;
+        }
+
+        public static void SetHeaderUnmodified(IHeaderDictionary headers, string key, StringValues? values)
+        {
+            if (headers == null)
+            {
+                throw new ArgumentNullException(nameof(headers));
+            }
+
+            if (string.IsNullOrEmpty(key))
+            {
+                throw new ArgumentNullException(nameof(key));
+            }
+            if (!values.HasValue || StringValues.IsNullOrEmpty(values.Value))
+            {
+                headers.Remove(key);
+            }
+            else
+            {
+                headers[key] = values.Value;
+            }
+        }
+
+        public static void AppendHeaderJoined(IHeaderDictionary headers, string key, params string[] values)
+        {
+            if (headers == null)
+            {
+                throw new ArgumentNullException(nameof(headers));
+            }
+
+            if (key == null)
+            {
+                throw new ArgumentNullException(nameof(key));
+            }
+
+            if (values == null || values.Length == 0)
+            {
+                return;
+            }
+
+            string existing = GetHeader(headers, key);
+            if (existing == null)
+            {
+                SetHeaderJoined(headers, key, values);
+            }
+            else
+            {
+                headers[key] = existing + "," + string.Join(",", values.Select(value => QuoteIfNeeded(value)));
+            }
+        }
+
+        public static void AppendHeaderUnmodified(IHeaderDictionary headers, string key, StringValues values)
+        {
+            if (headers == null)
+            {
+                throw new ArgumentNullException(nameof(headers));
+            }
+
+            if (key == null)
+            {
+                throw new ArgumentNullException(nameof(key));
+            }
+
+            if (values.Count == 0)
+            {
+                return;
+            }
+
+            var existing = GetHeaderUnmodified(headers, key);
+            SetHeaderUnmodified(headers, key, StringValues.Concat(existing, values));
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/Internal/PathStringHelper.cs b/src/Http/Http.Abstractions/src/Internal/PathStringHelper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b6cebb2b9c99cbdff5ef8822d453c5bacffe937b
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Internal/PathStringHelper.cs
@@ -0,0 +1,47 @@
+// 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.Internal
+{
+    internal class PathStringHelper
+    {
+        private static bool[] ValidPathChars = {
+            false, false, false, false, false, false, false, false,     // 0x00 - 0x07
+            false, false, false, false, false, false, false, false,     // 0x08 - 0x0F
+            false, false, false, false, false, false, false, false,     // 0x10 - 0x17
+            false, false, false, false, false, false, false, false,     // 0x18 - 0x1F
+            false, true,  false, false, true,  false, true,  true,      // 0x20 - 0x27
+            true,  true,  true,  true,  true,  true,  true,  true,      // 0x28 - 0x2F
+            true,  true,  true,  true,  true,  true,  true,  true,      // 0x30 - 0x37
+            true,  true,  true,  true,  false, true,  false, false,     // 0x38 - 0x3F
+            true,  true,  true,  true,  true,  true,  true,  true,      // 0x40 - 0x47
+            true,  true,  true,  true,  true,  true,  true,  true,      // 0x48 - 0x4F
+            true,  true,  true,  true,  true,  true,  true,  true,      // 0x50 - 0x57
+            true,  true,  true,  false, false, false, false, true,      // 0x58 - 0x5F
+            false, true,  true,  true,  true,  true,  true,  true,      // 0x60 - 0x67
+            true,  true,  true,  true,  true,  true,  true,  true,      // 0x68 - 0x6F
+            true,  true,  true,  true,  true,  true,  true,  true,      // 0x70 - 0x77
+            true,  true,  true,  false, false, false, true,  false,     // 0x78 - 0x7F
+        };
+
+        public static bool IsValidPathChar(char c)
+        {
+            return c < ValidPathChars.Length && ValidPathChars[c];
+        }
+
+        public static bool IsPercentEncodedChar(string str, int index)
+        {
+            return index < str.Length - 2
+                && str[index] == '%'
+                && IsHexadecimalChar(str[index + 1])
+                && IsHexadecimalChar(str[index + 2]);
+        }
+
+        public static bool IsHexadecimalChar(char c)
+        {
+            return ('0' <= c && c <= '9')
+                || ('A' <= c && c <= 'F')
+                || ('a' <= c && c <= 'f');
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj b/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..821b40cb191fcd70965f7df4623a8353ee1ef254
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj
@@ -0,0 +1,23 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+
+    <Description>ASP.NET Core HTTP object model for HTTP requests and responses and also common extension methods for registering middleware in an IApplicationBuilder.
+Commonly used types:
+Microsoft.AspNetCore.Builder.IApplicationBuilder
+Microsoft.AspNetCore.Http.HttpContext
+Microsoft.AspNetCore.Http.HttpRequest
+Microsoft.AspNetCore.Http.HttpResponse</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore</PackageTags>
+    <NoWarn>$(NoWarn);CS1591</NoWarn>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Http.Features" />
+    <Reference Include="Microsoft.Extensions.ActivatorUtilities.Sources" PrivateAssets="All" />
+    <Reference Include="System.Text.Encodings.Web" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/Http.Abstractions/src/PathString.cs b/src/Http/Http.Abstractions/src/PathString.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2a5960b661112bce3fb5544e03c1f20357f80872
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/PathString.cs
@@ -0,0 +1,477 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using System.Text;
+using Microsoft.AspNetCore.Http.Abstractions;
+using Microsoft.AspNetCore.Http.Internal;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Provides correct escaping for Path and PathBase values when needed to reconstruct a request or redirect URI string
+    /// </summary>
+    [TypeConverter(typeof(PathStringConverter))]
+    public struct PathString : IEquatable<PathString>
+    {
+        private static readonly char[] splitChar = { '/' };
+
+        /// <summary>
+        /// Represents the empty path. This field is read-only.
+        /// </summary>
+        public static readonly PathString Empty = new PathString(string.Empty);
+
+        private readonly string _value;
+
+        /// <summary>
+        /// Initalize the path string with a given value. This value must be in unescaped format. Use
+        /// PathString.FromUriComponent(value) if you have a path value which is in an escaped format.
+        /// </summary>
+        /// <param name="value">The unescaped path to be assigned to the Value property.</param>
+        public PathString(string value)
+        {
+            if (!string.IsNullOrEmpty(value) && value[0] != '/')
+            {
+                throw new ArgumentException(Resources.FormatException_PathMustStartWithSlash(nameof(value)), nameof(value));
+            }
+            _value = value;
+        }
+
+        /// <summary>
+        /// The unescaped path value
+        /// </summary>
+        public string Value
+        {
+            get { return _value; }
+        }
+
+        /// <summary>
+        /// True if the path is not empty
+        /// </summary>
+        public bool HasValue
+        {
+            get { return !string.IsNullOrEmpty(_value); }
+        }
+
+        /// <summary>
+        /// Provides the path string escaped in a way which is correct for combining into the URI representation.
+        /// </summary>
+        /// <returns>The escaped path value</returns>
+        public override string ToString()
+        {
+            return ToUriComponent();
+        }
+
+        /// <summary>
+        /// Provides the path string escaped in a way which is correct for combining into the URI representation.
+        /// </summary>
+        /// <returns>The escaped path value</returns>
+        public string ToUriComponent()
+        {
+            if (!HasValue)
+            {
+                return string.Empty;
+            }
+
+            StringBuilder buffer = null;
+
+            var start = 0;
+            var count = 0;
+            var requiresEscaping = false;
+            var i = 0;
+
+            while (i < _value.Length)
+            {
+                var isPercentEncodedChar = PathStringHelper.IsPercentEncodedChar(_value, i);
+                if (PathStringHelper.IsValidPathChar(_value[i]) || isPercentEncodedChar)
+                {
+                    if (requiresEscaping)
+                    {
+                        // the current segment requires escape
+                        if (buffer == null)
+                        {
+                            buffer = new StringBuilder(_value.Length * 3);
+                        }
+
+                        buffer.Append(Uri.EscapeDataString(_value.Substring(start, count)));
+
+                        requiresEscaping = false;
+                        start = i;
+                        count = 0;
+                    }
+
+                    if (isPercentEncodedChar)
+                    {
+                        count += 3;
+                        i += 3;
+                    }
+                    else
+                    {
+                        count++;
+                        i++;
+                    }
+                }
+                else
+                {
+                    if (!requiresEscaping)
+                    {
+                        // the current segument doesn't require escape
+                        if (buffer == null)
+                        {
+                            buffer = new StringBuilder(_value.Length * 3);
+                        }
+
+                        buffer.Append(_value, start, count);
+
+                        requiresEscaping = true;
+                        start = i;
+                        count = 0;
+                    }
+
+                    count++;
+                    i++;
+                }
+            }
+
+            if (count == _value.Length && !requiresEscaping)
+            {
+                return _value;
+            }
+            else
+            {
+                if (count > 0)
+                {
+                    if (buffer == null)
+                    {
+                        buffer = new StringBuilder(_value.Length * 3);
+                    }
+
+                    if (requiresEscaping)
+                    {
+                        buffer.Append(Uri.EscapeDataString(_value.Substring(start, count)));
+                    }
+                    else
+                    {
+                        buffer.Append(_value, start, count);
+                    }
+                }
+
+                return buffer.ToString();
+            }
+        }
+
+
+        /// <summary>
+        /// Returns an PathString given the path as it is escaped in the URI format. The string MUST NOT contain any
+        /// value that is not a path.
+        /// </summary>
+        /// <param name="uriComponent">The escaped path as it appears in the URI format.</param>
+        /// <returns>The resulting PathString</returns>
+        public static PathString FromUriComponent(string uriComponent)
+        {
+            // REVIEW: what is the exactly correct thing to do?
+            return new PathString(Uri.UnescapeDataString(uriComponent));
+        }
+
+        /// <summary>
+        /// Returns an PathString given the path as from a Uri object. Relative Uri objects are not supported.
+        /// </summary>
+        /// <param name="uri">The Uri object</param>
+        /// <returns>The resulting PathString</returns>
+        public static PathString FromUriComponent(Uri uri)
+        {
+            if (uri == null)
+            {
+                throw new ArgumentNullException(nameof(uri));
+            }
+
+            // REVIEW: what is the exactly correct thing to do?
+            return new PathString("/" + uri.GetComponents(UriComponents.Path, UriFormat.Unescaped));
+        }
+
+        /// <summary>
+        /// Determines whether the beginning of this <see cref="PathString"/> instance matches the specified <see cref="PathString"/>.
+        /// </summary>
+        /// <param name="other">The <see cref="PathString"/> to compare.</param>
+        /// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
+        public bool StartsWithSegments(PathString other)
+        {
+            return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase);
+        }
+
+        /// <summary>
+        /// Determines whether the beginning of this <see cref="PathString"/> instance matches the specified <see cref="PathString"/> when compared
+        /// using the specified comparison option.
+        /// </summary>
+        /// <param name="other">The <see cref="PathString"/> to compare.</param>
+        /// <param name="comparisonType">One of the enumeration values that determines how this <see cref="PathString"/> and value are compared.</param>
+        /// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
+        public bool StartsWithSegments(PathString other, StringComparison comparisonType)
+        {
+            var value1 = Value ?? string.Empty;
+            var value2 = other.Value ?? string.Empty;
+            if (value1.StartsWith(value2, comparisonType))
+            {
+                return value1.Length == value2.Length || value1[value2.Length] == '/';
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Determines whether the beginning of this <see cref="PathString"/> instance matches the specified <see cref="PathString"/> and returns
+        /// the remaining segments.
+        /// </summary>
+        /// <param name="other">The <see cref="PathString"/> to compare.</param>
+        /// <param name="remaining">The remaining segments after the match.</param>
+        /// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
+        public bool StartsWithSegments(PathString other, out PathString remaining)
+        {
+            return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase, out remaining);
+        }
+
+        /// <summary>
+        /// Determines whether the beginning of this <see cref="PathString"/> instance matches the specified <see cref="PathString"/> when compared
+        /// using the specified comparison option and returns the remaining segments.
+        /// </summary>
+        /// <param name="other">The <see cref="PathString"/> to compare.</param>
+        /// <param name="comparisonType">One of the enumeration values that determines how this <see cref="PathString"/> and value are compared.</param>
+        /// <param name="remaining">The remaining segments after the match.</param>
+        /// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
+        public bool StartsWithSegments(PathString other, StringComparison comparisonType, out PathString remaining)
+        {
+            var value1 = Value ?? string.Empty;
+            var value2 = other.Value ?? string.Empty;
+            if (value1.StartsWith(value2, comparisonType))
+            {
+                if (value1.Length == value2.Length || value1[value2.Length] == '/')
+                {
+                    remaining = new PathString(value1.Substring(value2.Length));
+                    return true;
+                }
+            }
+            remaining = Empty;
+            return false;
+        }
+
+        /// <summary>
+        /// Determines whether the beginning of this <see cref="PathString"/> instance matches the specified <see cref="PathString"/> and returns
+        /// the matched and remaining segments.
+        /// </summary>
+        /// <param name="other">The <see cref="PathString"/> to compare.</param>
+        /// <param name="matched">The matched segments with the original casing in the source value.</param>
+        /// <param name="remaining">The remaining segments after the match.</param>
+        /// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
+        public bool StartsWithSegments(PathString other, out PathString matched, out PathString remaining)
+        {
+            return StartsWithSegments(other, StringComparison.OrdinalIgnoreCase, out matched, out remaining);
+        }
+
+        /// <summary>
+        /// Determines whether the beginning of this <see cref="PathString"/> instance matches the specified <see cref="PathString"/> when compared
+        /// using the specified comparison option and returns the matched and remaining segments.
+        /// </summary>
+        /// <param name="other">The <see cref="PathString"/> to compare.</param>
+        /// <param name="comparisonType">One of the enumeration values that determines how this <see cref="PathString"/> and value are compared.</param>
+        /// <param name="matched">The matched segments with the original casing in the source value.</param>
+        /// <param name="remaining">The remaining segments after the match.</param>
+        /// <returns>true if value matches the beginning of this string; otherwise, false.</returns>
+        public bool StartsWithSegments(PathString other, StringComparison comparisonType, out PathString matched, out PathString remaining)
+        {
+            var value1 = Value ?? string.Empty;
+            var value2 = other.Value ?? string.Empty;
+            if (value1.StartsWith(value2, comparisonType))
+            {
+                if (value1.Length == value2.Length || value1[value2.Length] == '/')
+                {
+                    matched = new PathString(value1.Substring(0, value2.Length));
+                    remaining = new PathString(value1.Substring(value2.Length));
+                    return true;
+                }
+            }
+            remaining = Empty;
+            matched = Empty;
+            return false;
+        }
+
+        /// <summary>
+        /// Adds two PathString instances into a combined PathString value.
+        /// </summary>
+        /// <returns>The combined PathString value</returns>
+        public PathString Add(PathString other)
+        {
+            if (HasValue &&
+                other.HasValue &&
+                Value[Value.Length - 1] == '/')
+            {
+                // If the path string has a trailing slash and the other string has a leading slash, we need
+                // to trim one of them.
+                return new PathString(Value + other.Value.Substring(1));
+            }
+
+            return new PathString(Value + other.Value);
+        }
+
+        /// <summary>
+        /// Combines a PathString and QueryString into the joined URI formatted string value.
+        /// </summary>
+        /// <returns>The joined URI formatted string value</returns>
+        public string Add(QueryString other)
+        {
+            return ToUriComponent() + other.ToUriComponent();
+        }
+
+        /// <summary>
+        /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase.
+        /// </summary>
+        /// <param name="other">The second PathString for comparison.</param>
+        /// <returns>True if both PathString values are equal</returns>
+        public bool Equals(PathString other)
+        {
+            return Equals(other, StringComparison.OrdinalIgnoreCase);
+        }
+
+        /// <summary>
+        /// Compares this PathString value to another value using a specific StringComparison type
+        /// </summary>
+        /// <param name="other">The second PathString for comparison</param>
+        /// <param name="comparisonType">The StringComparison type to use</param>
+        /// <returns>True if both PathString values are equal</returns>
+        public bool Equals(PathString other, StringComparison comparisonType)
+        {
+            if (!HasValue && !other.HasValue)
+            {
+                return true;
+            }
+            return string.Equals(_value, other._value, comparisonType);
+        }
+
+        /// <summary>
+        /// Compares this PathString value to another value. The default comparison is StringComparison.OrdinalIgnoreCase.
+        /// </summary>
+        /// <param name="obj">The second PathString for comparison.</param>
+        /// <returns>True if both PathString values are equal</returns>
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(null, obj))
+            {
+                return !HasValue;
+            }
+            return obj is PathString && Equals((PathString)obj);
+        }
+
+        /// <summary>
+        /// Returns the hash code for the PathString value. The hash code is provided by the OrdinalIgnoreCase implementation.
+        /// </summary>
+        /// <returns>The hash code</returns>
+        public override int GetHashCode()
+        {
+            return (HasValue ? StringComparer.OrdinalIgnoreCase.GetHashCode(_value) : 0);
+        }
+
+        /// <summary>
+        /// Operator call through to Equals
+        /// </summary>
+        /// <param name="left">The left parameter</param>
+        /// <param name="right">The right parameter</param>
+        /// <returns>True if both PathString values are equal</returns>
+        public static bool operator ==(PathString left, PathString right)
+        {
+            return left.Equals(right);
+        }
+
+        /// <summary>
+        /// Operator call through to Equals
+        /// </summary>
+        /// <param name="left">The left parameter</param>
+        /// <param name="right">The right parameter</param>
+        /// <returns>True if both PathString values are not equal</returns>
+        public static bool operator !=(PathString left, PathString right)
+        {
+            return !left.Equals(right);
+        }
+
+        /// <summary>
+        /// </summary>
+        /// <param name="left">The left parameter</param>
+        /// <param name="right">The right parameter</param>
+        /// <returns>The ToString combination of both values</returns>
+        public static string operator +(string left, PathString right)
+        {
+            // This overload exists to prevent the implicit string<->PathString converter from
+            // trying to call the PathString+PathString operator for things that are not path strings.
+            return string.Concat(left, right.ToString());
+        }
+
+        /// <summary>
+        /// </summary>
+        /// <param name="left">The left parameter</param>
+        /// <param name="right">The right parameter</param>
+        /// <returns>The ToString combination of both values</returns>
+        public static string operator +(PathString left, string right)
+        {
+            // This overload exists to prevent the implicit string<->PathString converter from
+            // trying to call the PathString+PathString operator for things that are not path strings.
+            return string.Concat(left.ToString(), right);
+        }
+
+        /// <summary>
+        /// Operator call through to Add
+        /// </summary>
+        /// <param name="left">The left parameter</param>
+        /// <param name="right">The right parameter</param>
+        /// <returns>The PathString combination of both values</returns>
+        public static PathString operator +(PathString left, PathString right)
+        {
+            return left.Add(right);
+        }
+
+        /// <summary>
+        /// Operator call through to Add
+        /// </summary>
+        /// <param name="left">The left parameter</param>
+        /// <param name="right">The right parameter</param>
+        /// <returns>The PathString combination of both values</returns>
+        public static string operator +(PathString left, QueryString right)
+        {
+            return left.Add(right);
+        }
+
+        /// <summary>
+        /// Implicitly creates a new PathString from the given string.
+        /// </summary>
+        /// <param name="s"></param>
+        public static implicit operator PathString(string s)
+            => ConvertFromString(s);
+
+        /// <summary>
+        /// Implicitly calls ToString().
+        /// </summary>
+        /// <param name="path"></param>
+        public static implicit operator string(PathString path)
+            => path.ToString();
+
+        internal static PathString ConvertFromString(string s)
+            => string.IsNullOrEmpty(s) ? new PathString(s) : FromUriComponent(s);
+    }
+
+    internal class PathStringConverter : TypeConverter
+    {
+        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+            => sourceType == typeof(string) 
+            ? true 
+            : base.CanConvertFrom(context, sourceType);
+
+        public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+            => value is string 
+            ? PathString.ConvertFromString((string)value) 
+            : base.ConvertFrom(context, culture, value);
+
+        public override object ConvertTo(ITypeDescriptorContext context,
+           CultureInfo culture, object value, Type destinationType)
+            => destinationType == typeof(string) 
+            ? value.ToString() 
+            : base.ConvertTo(context, culture, value, destinationType);
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/Properties/AssemblyInfo.cs b/src/Http/Http.Abstractions/src/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2bdc2a912f1891322c34311475d6a511dd24e5b1
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Properties/AssemblyInfo.cs
@@ -0,0 +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.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Http.Abstractions.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
\ No newline at end of file
diff --git a/src/Http/Http.Abstractions/src/Properties/Resources.Designer.cs b/src/Http/Http.Abstractions/src/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6af7d138bee645559f6c98b50eb1900a2ad9ba66
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Properties/Resources.Designer.cs
@@ -0,0 +1,212 @@
+// <auto-generated />
+namespace Microsoft.AspNetCore.Http.Abstractions
+{
+    using System.Globalization;
+    using System.Reflection;
+    using System.Resources;
+
+    internal static class Resources
+    {
+        private static readonly ResourceManager _resourceManager
+            = new ResourceManager("Microsoft.AspNetCore.Http.Abstractions.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+        /// <summary>
+        /// '{0}' is not available.
+        /// </summary>
+        internal static string Exception_UseMiddlewareIServiceProviderNotAvailable
+        {
+            get => GetString("Exception_UseMiddlewareIServiceProviderNotAvailable");
+        }
+
+        /// <summary>
+        /// '{0}' is not available.
+        /// </summary>
+        internal static string FormatException_UseMiddlewareIServiceProviderNotAvailable(object p0)
+            => string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareIServiceProviderNotAvailable"), p0);
+
+        /// <summary>
+        /// No public '{0}' or '{1}' method found for middleware of type '{2}'.
+        /// </summary>
+        internal static string Exception_UseMiddlewareNoInvokeMethod
+        {
+            get => GetString("Exception_UseMiddlewareNoInvokeMethod");
+        }
+
+        /// <summary>
+        /// No public '{0}' or '{1}' method found for middleware of type '{2}'.
+        /// </summary>
+        internal static string FormatException_UseMiddlewareNoInvokeMethod(object p0, object p1, object p2)
+            => string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareNoInvokeMethod"), p0, p1, p2);
+
+        /// <summary>
+        /// '{0}' or '{1}' does not return an object of type '{2}'.
+        /// </summary>
+        internal static string Exception_UseMiddlewareNonTaskReturnType
+        {
+            get => GetString("Exception_UseMiddlewareNonTaskReturnType");
+        }
+
+        /// <summary>
+        /// '{0}' or '{1}' does not return an object of type '{2}'.
+        /// </summary>
+        internal static string FormatException_UseMiddlewareNonTaskReturnType(object p0, object p1, object p2)
+            => string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareNonTaskReturnType"), p0, p1, p2);
+
+        /// <summary>
+        /// The '{0}' or '{1}' method's first argument must be of type '{2}'.
+        /// </summary>
+        internal static string Exception_UseMiddlewareNoParameters
+        {
+            get => GetString("Exception_UseMiddlewareNoParameters");
+        }
+
+        /// <summary>
+        /// The '{0}' or '{1}' method's first argument must be of type '{2}'.
+        /// </summary>
+        internal static string FormatException_UseMiddlewareNoParameters(object p0, object p1, object p2)
+            => string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareNoParameters"), p0, p1, p2);
+
+        /// <summary>
+        /// Multiple public '{0}' or '{1}' methods are available.
+        /// </summary>
+        internal static string Exception_UseMiddleMutlipleInvokes
+        {
+            get => GetString("Exception_UseMiddleMutlipleInvokes");
+        }
+
+        /// <summary>
+        /// Multiple public '{0}' or '{1}' methods are available.
+        /// </summary>
+        internal static string FormatException_UseMiddleMutlipleInvokes(object p0, object p1)
+            => string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddleMutlipleInvokes"), p0, p1);
+
+        /// <summary>
+        /// The path in '{0}' must start with '/'.
+        /// </summary>
+        internal static string Exception_PathMustStartWithSlash
+        {
+            get => GetString("Exception_PathMustStartWithSlash");
+        }
+
+        /// <summary>
+        /// The path in '{0}' must start with '/'.
+        /// </summary>
+        internal static string FormatException_PathMustStartWithSlash(object p0)
+            => string.Format(CultureInfo.CurrentCulture, GetString("Exception_PathMustStartWithSlash"), p0);
+
+        /// <summary>
+        /// Unable to resolve service for type '{0}' while attempting to Invoke middleware '{1}'.
+        /// </summary>
+        internal static string Exception_InvokeMiddlewareNoService
+        {
+            get => GetString("Exception_InvokeMiddlewareNoService");
+        }
+
+        /// <summary>
+        /// Unable to resolve service for type '{0}' while attempting to Invoke middleware '{1}'.
+        /// </summary>
+        internal static string FormatException_InvokeMiddlewareNoService(object p0, object p1)
+            => string.Format(CultureInfo.CurrentCulture, GetString("Exception_InvokeMiddlewareNoService"), p0, p1);
+
+        /// <summary>
+        /// The '{0}' method must not have ref or out parameters.
+        /// </summary>
+        internal static string Exception_InvokeDoesNotSupportRefOrOutParams
+        {
+            get => GetString("Exception_InvokeDoesNotSupportRefOrOutParams");
+        }
+
+        /// <summary>
+        /// The '{0}' method must not have ref or out parameters.
+        /// </summary>
+        internal static string FormatException_InvokeDoesNotSupportRefOrOutParams(object p0)
+            => string.Format(CultureInfo.CurrentCulture, GetString("Exception_InvokeDoesNotSupportRefOrOutParams"), p0);
+
+        /// <summary>
+        /// The value must be greater than zero.
+        /// </summary>
+        internal static string Exception_PortMustBeGreaterThanZero
+        {
+            get => GetString("Exception_PortMustBeGreaterThanZero");
+        }
+
+        /// <summary>
+        /// The value must be greater than zero.
+        /// </summary>
+        internal static string FormatException_PortMustBeGreaterThanZero()
+            => GetString("Exception_PortMustBeGreaterThanZero");
+
+        /// <summary>
+        /// No service for type '{0}' has been registered.
+        /// </summary>
+        internal static string Exception_UseMiddlewareNoMiddlewareFactory
+        {
+            get => GetString("Exception_UseMiddlewareNoMiddlewareFactory");
+        }
+
+        /// <summary>
+        /// No service for type '{0}' has been registered.
+        /// </summary>
+        internal static string FormatException_UseMiddlewareNoMiddlewareFactory(object p0)
+            => string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareNoMiddlewareFactory"), p0);
+
+        /// <summary>
+        /// '{0}' failed to create middleware of type '{1}'.
+        /// </summary>
+        internal static string Exception_UseMiddlewareUnableToCreateMiddleware
+        {
+            get => GetString("Exception_UseMiddlewareUnableToCreateMiddleware");
+        }
+
+        /// <summary>
+        /// '{0}' failed to create middleware of type '{1}'.
+        /// </summary>
+        internal static string FormatException_UseMiddlewareUnableToCreateMiddleware(object p0, object p1)
+            => string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareUnableToCreateMiddleware"), p0, p1);
+
+        /// <summary>
+        /// Types that implement '{0}' do not support explicit arguments.
+        /// </summary>
+        internal static string Exception_UseMiddlewareExplicitArgumentsNotSupported
+        {
+            get => GetString("Exception_UseMiddlewareExplicitArgumentsNotSupported");
+        }
+
+        /// <summary>
+        /// Types that implement '{0}' do not support explicit arguments.
+        /// </summary>
+        internal static string FormatException_UseMiddlewareExplicitArgumentsNotSupported(object p0)
+            => string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareExplicitArgumentsNotSupported"), p0);
+
+        /// <summary>
+        /// Argument cannot be null or empty.
+        /// </summary>
+        internal static string ArgumentCannotBeNullOrEmpty
+        {
+            get => GetString("ArgumentCannotBeNullOrEmpty");
+        }
+
+        /// <summary>
+        /// Argument cannot be null or empty.
+        /// </summary>
+        internal static string FormatArgumentCannotBeNullOrEmpty()
+            => GetString("ArgumentCannotBeNullOrEmpty");
+
+        private static string GetString(string name, params string[] formatterNames)
+        {
+            var value = _resourceManager.GetString(name);
+
+            System.Diagnostics.Debug.Assert(value != null);
+
+            if (formatterNames != null)
+            {
+                for (var i = 0; i < formatterNames.Length; i++)
+                {
+                    value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+                }
+            }
+
+            return value;
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/QueryString.cs b/src/Http/Http.Abstractions/src/QueryString.cs
new file mode 100644
index 0000000000000000000000000000000000000000..772df8dfd9b7dbe2f4e00d1a13878aa05a5b5339
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/QueryString.cs
@@ -0,0 +1,261 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.Encodings.Web;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Provides correct handling for QueryString value when needed to reconstruct a request or redirect URI string
+    /// </summary>
+    public struct QueryString : IEquatable<QueryString>
+    {
+        /// <summary>
+        /// Represents the empty query string. This field is read-only.
+        /// </summary>
+        public static readonly QueryString Empty = new QueryString(string.Empty);
+
+        private readonly string _value;
+
+        /// <summary>
+        /// Initialize the query string with a given value. This value must be in escaped and delimited format with
+        /// a leading '?' character. 
+        /// </summary>
+        /// <param name="value">The query string to be assigned to the Value property.</param>
+        public QueryString(string value)
+        {
+            if (!string.IsNullOrEmpty(value) && value[0] != '?')
+            {
+                throw new ArgumentException("The leading '?' must be included for a non-empty query.", nameof(value));
+            }
+            _value = value;
+        }
+
+        /// <summary>
+        /// The escaped query string with the leading '?' character
+        /// </summary>
+        public string Value
+        {
+            get { return _value; }
+        }
+
+        /// <summary>
+        /// True if the query string is not empty
+        /// </summary>
+        public bool HasValue
+        {
+            get { return !string.IsNullOrEmpty(_value); }
+        }
+
+        /// <summary>
+        /// Provides the query string escaped in a way which is correct for combining into the URI representation. 
+        /// A leading '?' character will be included unless the Value is null or empty. Characters which are potentially
+        /// dangerous are escaped.
+        /// </summary>
+        /// <returns>The query string value</returns>
+        public override string ToString()
+        {
+            return ToUriComponent();
+        }
+
+        /// <summary>
+        /// Provides the query string escaped in a way which is correct for combining into the URI representation. 
+        /// A leading '?' character will be included unless the Value is null or empty. Characters which are potentially
+        /// dangerous are escaped.
+        /// </summary>
+        /// <returns>The query string value</returns>
+        public string ToUriComponent()
+        {
+            // Escape things properly so System.Uri doesn't mis-interpret the data.
+            return HasValue ? _value.Replace("#", "%23") : string.Empty;
+        }
+
+        /// <summary>
+        /// Returns an QueryString given the query as it is escaped in the URI format. The string MUST NOT contain any
+        /// value that is not a query.
+        /// </summary>
+        /// <param name="uriComponent">The escaped query as it appears in the URI format.</param>
+        /// <returns>The resulting QueryString</returns>
+        public static QueryString FromUriComponent(string uriComponent)
+        {
+            if (string.IsNullOrEmpty(uriComponent))
+            {
+                return new QueryString(string.Empty);
+            }
+            return new QueryString(uriComponent);
+        }
+
+        /// <summary>
+        /// Returns an QueryString given the query as from a Uri object. Relative Uri objects are not supported.
+        /// </summary>
+        /// <param name="uri">The Uri object</param>
+        /// <returns>The resulting QueryString</returns>
+        public static QueryString FromUriComponent(Uri uri)
+        {
+            if (uri == null)
+            {
+                throw new ArgumentNullException(nameof(uri));
+            }
+
+            string queryValue = uri.GetComponents(UriComponents.Query, UriFormat.UriEscaped);
+            if (!string.IsNullOrEmpty(queryValue))
+            {
+                queryValue = "?" + queryValue;
+            }
+            return new QueryString(queryValue);
+        }
+
+        /// <summary>
+        /// Create a query string with a single given parameter name and value.
+        /// </summary>
+        /// <param name="name">The un-encoded parameter name</param>
+        /// <param name="value">The un-encoded parameter value</param>
+        /// <returns>The resulting QueryString</returns>
+        public static QueryString Create(string name, string value)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            if (!string.IsNullOrEmpty(value))
+            {
+                value = UrlEncoder.Default.Encode(value);
+            }
+            return new QueryString($"?{UrlEncoder.Default.Encode(name)}={value}");
+        }
+
+        /// <summary>
+        /// Creates a query string composed from the given name value pairs.
+        /// </summary>
+        /// <param name="parameters"></param>
+        /// <returns>The resulting QueryString</returns>
+        public static QueryString Create(IEnumerable<KeyValuePair<string, string>> parameters)
+        {
+            var builder = new StringBuilder();
+            bool first = true;
+            foreach (var pair in parameters)
+            {
+                AppendKeyValuePair(builder, pair.Key, pair.Value, first);
+                first = false;
+            }
+
+            return new QueryString(builder.ToString());
+        }
+
+        /// <summary>
+        /// Creates a query string composed from the given name value pairs.
+        /// </summary>
+        /// <param name="parameters"></param>
+        /// <returns>The resulting QueryString</returns>
+        public static QueryString Create(IEnumerable<KeyValuePair<string, StringValues>> parameters)
+        {
+            var builder = new StringBuilder();
+            bool first = true;
+
+            foreach (var pair in parameters)
+            {
+                // If nothing in this pair.Values, append null value and continue
+                if (StringValues.IsNullOrEmpty(pair.Value))
+                {
+                    AppendKeyValuePair(builder, pair.Key, null, first);
+                    first = false;
+                    continue;
+                }
+                // Otherwise, loop through values in pair.Value
+                foreach (var value in pair.Value)
+                {
+                    AppendKeyValuePair(builder, pair.Key, value, first);
+                    first = false;
+                }
+            }
+
+            return new QueryString(builder.ToString());
+        }
+
+        public QueryString Add(QueryString other)
+        {
+            if (!HasValue || Value.Equals("?", StringComparison.Ordinal))
+            {
+                return other;
+            }
+            if (!other.HasValue || other.Value.Equals("?", StringComparison.Ordinal))
+            {
+                return this;
+            }
+
+            // ?name1=value1 Add ?name2=value2 returns ?name1=value1&name2=value2
+            return new QueryString(_value + "&" + other.Value.Substring(1));
+        }
+
+        public QueryString Add(string name, string value)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            if (!HasValue || Value.Equals("?", StringComparison.Ordinal))
+            {
+                return Create(name, value);
+            }
+
+            var builder = new StringBuilder(Value);
+            AppendKeyValuePair(builder, name, value, first: false);
+            return new QueryString(builder.ToString());
+        }
+
+        public bool Equals(QueryString other)
+        {
+            if (!HasValue && !other.HasValue)
+            {
+                return true;
+            }
+            return string.Equals(_value, other._value, StringComparison.Ordinal);
+        }
+
+        public override bool Equals(object obj)
+        {
+            if (ReferenceEquals(null, obj))
+            {
+                return !HasValue;
+            }
+            return obj is QueryString && Equals((QueryString)obj);
+        }
+
+        public override int GetHashCode()
+        {
+            return (HasValue ? _value.GetHashCode() : 0);
+        }
+
+        public static bool operator ==(QueryString left, QueryString right)
+        {
+            return left.Equals(right);
+        }
+
+        public static bool operator !=(QueryString left, QueryString right)
+        {
+            return !left.Equals(right);
+        }
+
+        public static QueryString operator +(QueryString left, QueryString right)
+        {
+            return left.Add(right);
+        }
+
+        private static void AppendKeyValuePair(StringBuilder builder, string key, string value, bool first)
+        {
+            builder.Append(first ? "?" : "&");
+            builder.Append(UrlEncoder.Default.Encode(key));
+            builder.Append("=");
+            if (!string.IsNullOrEmpty(value))
+            {
+                builder.Append(UrlEncoder.Default.Encode(value));
+            }
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/RequestDelegate.cs b/src/Http/Http.Abstractions/src/RequestDelegate.cs
new file mode 100644
index 0000000000000000000000000000000000000000..aecf353b29d8bbfb337f1359991cb920865a2973
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/RequestDelegate.cs
@@ -0,0 +1,14 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// A function that can process an HTTP request.
+    /// </summary>
+    /// <param name="context">The <see cref="HttpContext"/> for the request.</param>
+    /// <returns>A task that represents the completion of request processing.</returns>
+    public delegate Task RequestDelegate(HttpContext context);
+}
\ No newline at end of file
diff --git a/src/Http/Http.Abstractions/src/Resources.resx b/src/Http/Http.Abstractions/src/Resources.resx
new file mode 100644
index 0000000000000000000000000000000000000000..dfdfeaf7d11e961ff8133bf2275743fe1f02b236
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/Resources.resx
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="Exception_UseMiddlewareIServiceProviderNotAvailable" xml:space="preserve">
+    <value>'{0}' is not available.</value>
+  </data>
+  <data name="Exception_UseMiddlewareNoInvokeMethod" xml:space="preserve">
+    <value>No public '{0}' or '{1}' method found for middleware of type '{2}'.</value>
+  </data>
+  <data name="Exception_UseMiddlewareNonTaskReturnType" xml:space="preserve">
+    <value>'{0}' or '{1}' does not return an object of type '{2}'.</value>
+  </data>
+  <data name="Exception_UseMiddlewareNoParameters" xml:space="preserve">
+    <value>The '{0}' or '{1}' method's first argument must be of type '{2}'.</value>
+  </data>
+  <data name="Exception_UseMiddleMutlipleInvokes" xml:space="preserve">
+    <value>Multiple public '{0}' or '{1}' methods are available.</value>
+  </data>
+  <data name="Exception_PathMustStartWithSlash" xml:space="preserve">
+    <value>The path in '{0}' must start with '/'.</value>
+  </data>
+  <data name="Exception_InvokeMiddlewareNoService" xml:space="preserve">
+    <value>Unable to resolve service for type '{0}' while attempting to Invoke middleware '{1}'.</value>
+  </data>
+  <data name="Exception_InvokeDoesNotSupportRefOrOutParams" xml:space="preserve">
+    <value>The '{0}' method must not have ref or out parameters.</value>
+  </data>
+  <data name="Exception_PortMustBeGreaterThanZero" xml:space="preserve">
+    <value>The value must be greater than zero.</value>
+  </data>
+  <data name="Exception_UseMiddlewareNoMiddlewareFactory" xml:space="preserve">
+    <value>No service for type '{0}' has been registered.</value>
+  </data>
+  <data name="Exception_UseMiddlewareUnableToCreateMiddleware" xml:space="preserve">
+    <value>'{0}' failed to create middleware of type '{1}'.</value>
+  </data>
+  <data name="Exception_UseMiddlewareExplicitArgumentsNotSupported" xml:space="preserve">
+    <value>Types that implement '{0}' do not support explicit arguments.</value>
+  </data>
+  <data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
+    <value>Argument cannot be null or empty.</value>
+  </data>
+</root>
\ No newline at end of file
diff --git a/src/Http/Http.Abstractions/src/StatusCodes.cs b/src/Http/Http.Abstractions/src/StatusCodes.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3261bce2f2995d37c9c12d3b91c5774a2dbeb9f8
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/StatusCodes.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.
+
+namespace Microsoft.AspNetCore.Http
+{
+    // Status Codes listed at http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+    public static class StatusCodes
+    {
+        public const int Status100Continue = 100;
+        public const int Status101SwitchingProtocols = 101;
+        public const int Status102Processing = 102;
+
+        public const int Status200OK = 200;
+        public const int Status201Created = 201;
+        public const int Status202Accepted = 202;
+        public const int Status203NonAuthoritative = 203;
+        public const int Status204NoContent = 204;
+        public const int Status205ResetContent = 205;
+        public const int Status206PartialContent = 206;
+        public const int Status207MultiStatus = 207;
+        public const int Status208AlreadyReported = 208;
+        public const int Status226IMUsed = 226;
+
+        public const int Status300MultipleChoices = 300;
+        public const int Status301MovedPermanently = 301;
+        public const int Status302Found = 302;
+        public const int Status303SeeOther = 303;
+        public const int Status304NotModified = 304;
+        public const int Status305UseProxy = 305;
+        public const int Status306SwitchProxy = 306; // RFC 2616, removed
+        public const int Status307TemporaryRedirect = 307;
+        public const int Status308PermanentRedirect = 308;
+
+        public const int Status400BadRequest = 400;
+        public const int Status401Unauthorized = 401;
+        public const int Status402PaymentRequired = 402;
+        public const int Status403Forbidden = 403;
+        public const int Status404NotFound = 404;
+        public const int Status405MethodNotAllowed = 405;
+        public const int Status406NotAcceptable = 406;
+        public const int Status407ProxyAuthenticationRequired = 407;
+        public const int Status408RequestTimeout = 408;
+        public const int Status409Conflict = 409;
+        public const int Status410Gone = 410;
+        public const int Status411LengthRequired = 411;
+        public const int Status412PreconditionFailed = 412;
+        public const int Status413RequestEntityTooLarge = 413; // RFC 2616, renamed
+        public const int Status413PayloadTooLarge = 413; // RFC 7231
+        public const int Status414RequestUriTooLong = 414; // RFC 2616, renamed
+        public const int Status414UriTooLong = 414; // RFC 7231
+        public const int Status415UnsupportedMediaType = 415;
+        public const int Status416RequestedRangeNotSatisfiable = 416; // RFC 2616, renamed
+        public const int Status416RangeNotSatisfiable = 416; // RFC 7233
+        public const int Status417ExpectationFailed = 417;
+        public const int Status418ImATeapot = 418;
+        public const int Status419AuthenticationTimeout = 419; // Not defined in any RFC
+        public const int Status421MisdirectedRequest = 421;
+        public const int Status422UnprocessableEntity = 422;
+        public const int Status423Locked = 423;
+        public const int Status424FailedDependency = 424;
+        public const int Status426UpgradeRequired = 426;
+        public const int Status428PreconditionRequired = 428;
+        public const int Status429TooManyRequests = 429;
+        public const int Status431RequestHeaderFieldsTooLarge = 431;
+        public const int Status451UnavailableForLegalReasons = 451;
+
+        public const int Status500InternalServerError = 500;
+        public const int Status501NotImplemented = 501;
+        public const int Status502BadGateway = 502;
+        public const int Status503ServiceUnavailable = 503;
+        public const int Status504GatewayTimeout = 504;
+        public const int Status505HttpVersionNotsupported = 505;
+        public const int Status506VariantAlsoNegotiates = 506;
+        public const int Status507InsufficientStorage = 507;
+        public const int Status508LoopDetected = 508;
+        public const int Status510NotExtended = 510;
+        public const int Status511NetworkAuthenticationRequired = 511;
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/WebSocketManager.cs b/src/Http/Http.Abstractions/src/WebSocketManager.cs
new file mode 100644
index 0000000000000000000000000000000000000000..79afefa5c0d4c18ab2faab05fd7fa1fc4743660e
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/WebSocketManager.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.Collections.Generic;
+using System.Net.WebSockets;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Manages the establishment of WebSocket connections for a specific HTTP request. 
+    /// </summary>
+    public abstract class WebSocketManager
+    {
+        /// <summary>
+        /// Gets a value indicating whether the request is a WebSocket establishment request.
+        /// </summary>
+        public abstract bool IsWebSocketRequest { get; }
+
+        /// <summary>
+        /// Gets the list of requested WebSocket sub-protocols.
+        /// </summary>
+        public abstract IList<string> WebSocketRequestedProtocols { get; }
+
+        /// <summary>
+        /// Transitions the request to a WebSocket connection.
+        /// </summary>
+        /// <returns>A task representing the completion of the transition.</returns>
+        public virtual Task<WebSocket> AcceptWebSocketAsync()
+        {
+            return AcceptWebSocketAsync(subProtocol: null);
+        }
+
+        /// <summary>
+        /// Transitions the request to a WebSocket connection using the specified sub-protocol.
+        /// </summary>
+        /// <param name="subProtocol">The sub-protocol to use.</param>
+        /// <returns>A task representing the completion of the transition.</returns>
+        public abstract Task<WebSocket> AcceptWebSocketAsync(string subProtocol);
+    }
+}
diff --git a/src/Http/Http.Abstractions/src/baseline.netcore.json b/src/Http/Http.Abstractions/src/baseline.netcore.json
new file mode 100644
index 0000000000000000000000000000000000000000..f407fb08e6338f31c1afd1a2bb5a1d38fab93ef4
--- /dev/null
+++ b/src/Http/Http.Abstractions/src/baseline.netcore.json
@@ -0,0 +1,5020 @@
+{
+  "AssemblyIdentity": "Microsoft.AspNetCore.Http.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+  "Types": [
+    {
+      "Name": "Microsoft.AspNetCore.Builder.MapExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Map",
+          "Parameters": [
+            {
+              "Name": "app",
+              "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+            },
+            {
+              "Name": "pathMatch",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            },
+            {
+              "Name": "configuration",
+              "Type": "System.Action<Microsoft.AspNetCore.Builder.IApplicationBuilder>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Builder.MapWhenExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "MapWhen",
+          "Parameters": [
+            {
+              "Name": "app",
+              "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+            },
+            {
+              "Name": "predicate",
+              "Type": "System.Func<Microsoft.AspNetCore.Http.HttpContext, System.Boolean>"
+            },
+            {
+              "Name": "configuration",
+              "Type": "System.Action<Microsoft.AspNetCore.Builder.IApplicationBuilder>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Builder.RunExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Run",
+          "Parameters": [
+            {
+              "Name": "app",
+              "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+            },
+            {
+              "Name": "handler",
+              "Type": "Microsoft.AspNetCore.Http.RequestDelegate"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Builder.UseExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Use",
+          "Parameters": [
+            {
+              "Name": "app",
+              "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+            },
+            {
+              "Name": "middleware",
+              "Type": "System.Func<Microsoft.AspNetCore.Http.HttpContext, System.Func<System.Threading.Tasks.Task>, System.Threading.Tasks.Task>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Builder.UseMiddlewareExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "UseMiddleware<T0>",
+          "Parameters": [
+            {
+              "Name": "app",
+              "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+            },
+            {
+              "Name": "args",
+              "Type": "System.Object[]",
+              "IsParams": true
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "TMiddleware",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseMiddleware",
+          "Parameters": [
+            {
+              "Name": "app",
+              "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+            },
+            {
+              "Name": "middleware",
+              "Type": "System.Type"
+            },
+            {
+              "Name": "args",
+              "Type": "System.Object[]",
+              "IsParams": true
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Builder.UsePathBaseExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "UsePathBase",
+          "Parameters": [
+            {
+              "Name": "app",
+              "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+            },
+            {
+              "Name": "pathBase",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Builder.UseWhenExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "UseWhen",
+          "Parameters": [
+            {
+              "Name": "app",
+              "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+            },
+            {
+              "Name": "predicate",
+              "Type": "System.Func<Microsoft.AspNetCore.Http.HttpContext, System.Boolean>"
+            },
+            {
+              "Name": "configuration",
+              "Type": "System.Action<Microsoft.AspNetCore.Builder.IApplicationBuilder>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_ApplicationServices",
+          "Parameters": [],
+          "ReturnType": "System.IServiceProvider",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ApplicationServices",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.IServiceProvider"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ServerFeatures",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Properties",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.String, System.Object>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Use",
+          "Parameters": [
+            {
+              "Name": "middleware",
+              "Type": "System.Func<Microsoft.AspNetCore.Http.RequestDelegate, Microsoft.AspNetCore.Http.RequestDelegate>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "New",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Build",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.RequestDelegate",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Builder.Extensions.MapMiddleware",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Invoke",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "next",
+              "Type": "Microsoft.AspNetCore.Http.RequestDelegate"
+            },
+            {
+              "Name": "options",
+              "Type": "Microsoft.AspNetCore.Builder.Extensions.MapOptions"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Builder.Extensions.MapOptions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_PathMatch",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_PathMatch",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Branch",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.RequestDelegate",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Branch",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.RequestDelegate"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Builder.Extensions.MapWhenMiddleware",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Invoke",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "next",
+              "Type": "Microsoft.AspNetCore.Http.RequestDelegate"
+            },
+            {
+              "Name": "options",
+              "Type": "Microsoft.AspNetCore.Builder.Extensions.MapWhenOptions"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Builder.Extensions.MapWhenOptions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Predicate",
+          "Parameters": [],
+          "ReturnType": "System.Func<Microsoft.AspNetCore.Http.HttpContext, System.Boolean>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Predicate",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Func<Microsoft.AspNetCore.Http.HttpContext, System.Boolean>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Branch",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.RequestDelegate",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Branch",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.RequestDelegate"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Builder.Extensions.UsePathBaseMiddleware",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Invoke",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "next",
+              "Type": "Microsoft.AspNetCore.Http.RequestDelegate"
+            },
+            {
+              "Name": "pathBase",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.ConnectionInfo",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Id",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Id",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_RemoteIpAddress",
+          "Parameters": [],
+          "ReturnType": "System.Net.IPAddress",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RemoteIpAddress",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Net.IPAddress"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_RemotePort",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RemotePort",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_LocalIpAddress",
+          "Parameters": [],
+          "ReturnType": "System.Net.IPAddress",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_LocalIpAddress",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Net.IPAddress"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_LocalPort",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_LocalPort",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ClientCertificate",
+          "Parameters": [],
+          "ReturnType": "System.Security.Cryptography.X509Certificates.X509Certificate2",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ClientCertificate",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Security.Cryptography.X509Certificates.X509Certificate2"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetClientCertificateAsync",
+          "Parameters": [
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Security.Cryptography.X509Certificates.X509Certificate2>",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Protected",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.CookieBuilder",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Name",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Name",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Path",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Path",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Domain",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Domain",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HttpOnly",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_HttpOnly",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_SameSite",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.SameSiteMode",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_SameSite",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.SameSiteMode"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_SecurePolicy",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.CookieSecurePolicy",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_SecurePolicy",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.CookieSecurePolicy"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Expiration",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.TimeSpan>",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Expiration",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.TimeSpan>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MaxAge",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.TimeSpan>",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_MaxAge",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.TimeSpan>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IsEssential",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_IsEssential",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Build",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.CookieOptions",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Build",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "expiresFrom",
+              "Type": "System.DateTimeOffset"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.CookieOptions",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.CookieSecurePolicy",
+      "Visibility": "Public",
+      "Kind": "Enumeration",
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Field",
+          "Name": "SameAsRequest",
+          "Parameters": [],
+          "GenericParameter": [],
+          "Literal": "0"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Always",
+          "Parameters": [],
+          "GenericParameter": [],
+          "Literal": "1"
+        },
+        {
+          "Kind": "Field",
+          "Name": "None",
+          "Parameters": [],
+          "GenericParameter": [],
+          "Literal": "2"
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.HeaderDictionaryExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Append",
+          "Parameters": [
+            {
+              "Name": "headers",
+              "Type": "Microsoft.AspNetCore.Http.IHeaderDictionary"
+            },
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringValues"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AppendCommaSeparatedValues",
+          "Parameters": [
+            {
+              "Name": "headers",
+              "Type": "Microsoft.AspNetCore.Http.IHeaderDictionary"
+            },
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "values",
+              "Type": "System.String[]",
+              "IsParams": true
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetCommaSeparatedValues",
+          "Parameters": [
+            {
+              "Name": "headers",
+              "Type": "Microsoft.AspNetCore.Http.IHeaderDictionary"
+            },
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.String[]",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SetCommaSeparatedValues",
+          "Parameters": [
+            {
+              "Name": "headers",
+              "Type": "Microsoft.AspNetCore.Http.IHeaderDictionary"
+            },
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "values",
+              "Type": "System.String[]",
+              "IsParams": true
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.HttpResponseWritingExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "WriteAsync",
+          "Parameters": [
+            {
+              "Name": "response",
+              "Type": "Microsoft.AspNetCore.Http.HttpResponse"
+            },
+            {
+              "Name": "text",
+              "Type": "System.String"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "WriteAsync",
+          "Parameters": [
+            {
+              "Name": "response",
+              "Type": "Microsoft.AspNetCore.Http.HttpResponse"
+            },
+            {
+              "Name": "text",
+              "Type": "System.String"
+            },
+            {
+              "Name": "encoding",
+              "Type": "System.Text.Encoding"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.FragmentString",
+      "Visibility": "Public",
+      "Kind": "Struct",
+      "Sealed": true,
+      "ImplementedInterfaces": [
+        "System.IEquatable<Microsoft.AspNetCore.Http.FragmentString>"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Value",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HasValue",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToUriComponent",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "FromUriComponent",
+          "Parameters": [
+            {
+              "Name": "uriComponent",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.FragmentString",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "FromUriComponent",
+          "Parameters": [
+            {
+              "Name": "uri",
+              "Type": "System.Uri"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.FragmentString",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "other",
+              "Type": "Microsoft.AspNetCore.Http.FragmentString"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.IEquatable<Microsoft.AspNetCore.Http.FragmentString>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "obj",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHashCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "op_Equality",
+          "Parameters": [
+            {
+              "Name": "left",
+              "Type": "Microsoft.AspNetCore.Http.FragmentString"
+            },
+            {
+              "Name": "right",
+              "Type": "Microsoft.AspNetCore.Http.FragmentString"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "op_Inequality",
+          "Parameters": [
+            {
+              "Name": "left",
+              "Type": "Microsoft.AspNetCore.Http.FragmentString"
+            },
+            {
+              "Name": "right",
+              "Type": "Microsoft.AspNetCore.Http.FragmentString"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Empty",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.FragmentString",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.HostString",
+      "Visibility": "Public",
+      "Kind": "Struct",
+      "Sealed": true,
+      "ImplementedInterfaces": [
+        "System.IEquatable<Microsoft.AspNetCore.Http.HostString>"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Value",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HasValue",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Host",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Port",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Int32>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToUriComponent",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "FromUriComponent",
+          "Parameters": [
+            {
+              "Name": "uriComponent",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.HostString",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "FromUriComponent",
+          "Parameters": [
+            {
+              "Name": "uri",
+              "Type": "System.Uri"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.HostString",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "MatchesAny",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringSegment"
+            },
+            {
+              "Name": "patterns",
+              "Type": "System.Collections.Generic.IList<Microsoft.Extensions.Primitives.StringSegment>"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "other",
+              "Type": "Microsoft.AspNetCore.Http.HostString"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.IEquatable<Microsoft.AspNetCore.Http.HostString>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "obj",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHashCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "op_Equality",
+          "Parameters": [
+            {
+              "Name": "left",
+              "Type": "Microsoft.AspNetCore.Http.HostString"
+            },
+            {
+              "Name": "right",
+              "Type": "Microsoft.AspNetCore.Http.HostString"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "op_Inequality",
+          "Parameters": [
+            {
+              "Name": "left",
+              "Type": "Microsoft.AspNetCore.Http.HostString"
+            },
+            {
+              "Name": "right",
+              "Type": "Microsoft.AspNetCore.Http.HostString"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "host",
+              "Type": "System.String"
+            },
+            {
+              "Name": "port",
+              "Type": "System.Int32"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.HttpContext",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Features",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Request",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.HttpRequest",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Response",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.HttpResponse",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Connection",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.ConnectionInfo",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_WebSockets",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.WebSocketManager",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Authentication",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.Authentication.AuthenticationManager",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_User",
+          "Parameters": [],
+          "ReturnType": "System.Security.Claims.ClaimsPrincipal",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_User",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Items",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.Object, System.Object>",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Items",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Collections.Generic.IDictionary<System.Object, System.Object>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_RequestServices",
+          "Parameters": [],
+          "ReturnType": "System.IServiceProvider",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RequestServices",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.IServiceProvider"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_RequestAborted",
+          "Parameters": [],
+          "ReturnType": "System.Threading.CancellationToken",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RequestAborted",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_TraceIdentifier",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_TraceIdentifier",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Session",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.ISession",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Session",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.ISession"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Abort",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Protected",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.HttpMethods",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "IsConnect",
+          "Parameters": [
+            {
+              "Name": "method",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "IsDelete",
+          "Parameters": [
+            {
+              "Name": "method",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "IsGet",
+          "Parameters": [
+            {
+              "Name": "method",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "IsHead",
+          "Parameters": [
+            {
+              "Name": "method",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "IsOptions",
+          "Parameters": [
+            {
+              "Name": "method",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "IsPatch",
+          "Parameters": [
+            {
+              "Name": "method",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "IsPost",
+          "Parameters": [
+            {
+              "Name": "method",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "IsPut",
+          "Parameters": [
+            {
+              "Name": "method",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "IsTrace",
+          "Parameters": [
+            {
+              "Name": "method",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Connect",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Delete",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Get",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Head",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Options",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Patch",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Post",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Put",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Trace",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.HttpRequest",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_HttpContext",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.HttpContext",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Method",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Method",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Scheme",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Scheme",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IsHttps",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_IsHttps",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Host",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.HostString",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Host",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.HostString"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_PathBase",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_PathBase",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Path",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Path",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_QueryString",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.QueryString",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_QueryString",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.QueryString"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Query",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IQueryCollection",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Query",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.IQueryCollection"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Protocol",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Protocol",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Headers",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IHeaderDictionary",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Cookies",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IRequestCookieCollection",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Cookies",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.IRequestCookieCollection"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentLength",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Int64>",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentLength",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.Int64>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentType",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentType",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Body",
+          "Parameters": [],
+          "ReturnType": "System.IO.Stream",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Body",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.IO.Stream"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HasFormContentType",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Form",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IFormCollection",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Form",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.IFormCollection"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ReadFormAsync",
+          "Parameters": [
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Http.IFormCollection>",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Protected",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.HttpResponse",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_HttpContext",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.HttpContext",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_StatusCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_StatusCode",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Headers",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IHeaderDictionary",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Body",
+          "Parameters": [],
+          "ReturnType": "System.IO.Stream",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Body",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.IO.Stream"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentLength",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Int64>",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentLength",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.Int64>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentType",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentType",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Cookies",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IResponseCookies",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HasStarted",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnStarting",
+          "Parameters": [
+            {
+              "Name": "callback",
+              "Type": "System.Func<System.Object, System.Threading.Tasks.Task>"
+            },
+            {
+              "Name": "state",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnStarting",
+          "Parameters": [
+            {
+              "Name": "callback",
+              "Type": "System.Func<System.Threading.Tasks.Task>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnCompleted",
+          "Parameters": [
+            {
+              "Name": "callback",
+              "Type": "System.Func<System.Object, System.Threading.Tasks.Task>"
+            },
+            {
+              "Name": "state",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "RegisterForDispose",
+          "Parameters": [
+            {
+              "Name": "disposable",
+              "Type": "System.IDisposable"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnCompleted",
+          "Parameters": [
+            {
+              "Name": "callback",
+              "Type": "System.Func<System.Threading.Tasks.Task>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Redirect",
+          "Parameters": [
+            {
+              "Name": "location",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Redirect",
+          "Parameters": [
+            {
+              "Name": "location",
+              "Type": "System.String"
+            },
+            {
+              "Name": "permanent",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Protected",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.IHttpContextAccessor",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_HttpContext",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.HttpContext",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_HttpContext",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.IHttpContextFactory",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Create",
+          "Parameters": [
+            {
+              "Name": "featureCollection",
+              "Type": "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.HttpContext",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Dispose",
+          "Parameters": [
+            {
+              "Name": "httpContext",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.IMiddleware",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "InvokeAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "next",
+              "Type": "Microsoft.AspNetCore.Http.RequestDelegate"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.IMiddlewareFactory",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Create",
+          "Parameters": [
+            {
+              "Name": "middlewareType",
+              "Type": "System.Type"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.IMiddleware",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Release",
+          "Parameters": [
+            {
+              "Name": "middleware",
+              "Type": "Microsoft.AspNetCore.Http.IMiddleware"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.PathString",
+      "Visibility": "Public",
+      "Kind": "Struct",
+      "Sealed": true,
+      "ImplementedInterfaces": [
+        "System.IEquatable<Microsoft.AspNetCore.Http.PathString>"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Value",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HasValue",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToUriComponent",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "FromUriComponent",
+          "Parameters": [
+            {
+              "Name": "uriComponent",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "FromUriComponent",
+          "Parameters": [
+            {
+              "Name": "uri",
+              "Type": "System.Uri"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "StartsWithSegments",
+          "Parameters": [
+            {
+              "Name": "other",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "StartsWithSegments",
+          "Parameters": [
+            {
+              "Name": "other",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            },
+            {
+              "Name": "comparisonType",
+              "Type": "System.StringComparison"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "StartsWithSegments",
+          "Parameters": [
+            {
+              "Name": "other",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            },
+            {
+              "Name": "remaining",
+              "Type": "Microsoft.AspNetCore.Http.PathString",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "StartsWithSegments",
+          "Parameters": [
+            {
+              "Name": "other",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            },
+            {
+              "Name": "comparisonType",
+              "Type": "System.StringComparison"
+            },
+            {
+              "Name": "remaining",
+              "Type": "Microsoft.AspNetCore.Http.PathString",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "StartsWithSegments",
+          "Parameters": [
+            {
+              "Name": "other",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            },
+            {
+              "Name": "matched",
+              "Type": "Microsoft.AspNetCore.Http.PathString",
+              "Direction": "Out"
+            },
+            {
+              "Name": "remaining",
+              "Type": "Microsoft.AspNetCore.Http.PathString",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "StartsWithSegments",
+          "Parameters": [
+            {
+              "Name": "other",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            },
+            {
+              "Name": "comparisonType",
+              "Type": "System.StringComparison"
+            },
+            {
+              "Name": "matched",
+              "Type": "Microsoft.AspNetCore.Http.PathString",
+              "Direction": "Out"
+            },
+            {
+              "Name": "remaining",
+              "Type": "Microsoft.AspNetCore.Http.PathString",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Add",
+          "Parameters": [
+            {
+              "Name": "other",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Add",
+          "Parameters": [
+            {
+              "Name": "other",
+              "Type": "Microsoft.AspNetCore.Http.QueryString"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "other",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.IEquatable<Microsoft.AspNetCore.Http.PathString>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "other",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            },
+            {
+              "Name": "comparisonType",
+              "Type": "System.StringComparison"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "obj",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHashCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "op_Equality",
+          "Parameters": [
+            {
+              "Name": "left",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            },
+            {
+              "Name": "right",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "op_Inequality",
+          "Parameters": [
+            {
+              "Name": "left",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            },
+            {
+              "Name": "right",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "op_Addition",
+          "Parameters": [
+            {
+              "Name": "left",
+              "Type": "System.String"
+            },
+            {
+              "Name": "right",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "op_Addition",
+          "Parameters": [
+            {
+              "Name": "left",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            },
+            {
+              "Name": "right",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "op_Addition",
+          "Parameters": [
+            {
+              "Name": "left",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            },
+            {
+              "Name": "right",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "op_Addition",
+          "Parameters": [
+            {
+              "Name": "left",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            },
+            {
+              "Name": "right",
+              "Type": "Microsoft.AspNetCore.Http.QueryString"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "op_Implicit",
+          "Parameters": [
+            {
+              "Name": "s",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "op_Implicit",
+          "Parameters": [
+            {
+              "Name": "path",
+              "Type": "Microsoft.AspNetCore.Http.PathString"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Empty",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.PathString",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.QueryString",
+      "Visibility": "Public",
+      "Kind": "Struct",
+      "Sealed": true,
+      "ImplementedInterfaces": [
+        "System.IEquatable<Microsoft.AspNetCore.Http.QueryString>"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Value",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HasValue",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToUriComponent",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "FromUriComponent",
+          "Parameters": [
+            {
+              "Name": "uriComponent",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.QueryString",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "FromUriComponent",
+          "Parameters": [
+            {
+              "Name": "uri",
+              "Type": "System.Uri"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.QueryString",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Create",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.QueryString",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Create",
+          "Parameters": [
+            {
+              "Name": "parameters",
+              "Type": "System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.String, System.String>>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.QueryString",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Create",
+          "Parameters": [
+            {
+              "Name": "parameters",
+              "Type": "System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.QueryString",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Add",
+          "Parameters": [
+            {
+              "Name": "other",
+              "Type": "Microsoft.AspNetCore.Http.QueryString"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.QueryString",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Add",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.QueryString",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "other",
+              "Type": "Microsoft.AspNetCore.Http.QueryString"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.IEquatable<Microsoft.AspNetCore.Http.QueryString>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "obj",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHashCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "op_Equality",
+          "Parameters": [
+            {
+              "Name": "left",
+              "Type": "Microsoft.AspNetCore.Http.QueryString"
+            },
+            {
+              "Name": "right",
+              "Type": "Microsoft.AspNetCore.Http.QueryString"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "op_Inequality",
+          "Parameters": [
+            {
+              "Name": "left",
+              "Type": "Microsoft.AspNetCore.Http.QueryString"
+            },
+            {
+              "Name": "right",
+              "Type": "Microsoft.AspNetCore.Http.QueryString"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "op_Addition",
+          "Parameters": [
+            {
+              "Name": "left",
+              "Type": "Microsoft.AspNetCore.Http.QueryString"
+            },
+            {
+              "Name": "right",
+              "Type": "Microsoft.AspNetCore.Http.QueryString"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.QueryString",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Empty",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.QueryString",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.RequestDelegate",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Sealed": true,
+      "BaseType": "System.MulticastDelegate",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Invoke",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "BeginInvoke",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            },
+            {
+              "Name": "callback",
+              "Type": "System.AsyncCallback"
+            },
+            {
+              "Name": "object",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.IAsyncResult",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "EndInvoke",
+          "Parameters": [
+            {
+              "Name": "result",
+              "Type": "System.IAsyncResult"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "object",
+              "Type": "System.Object"
+            },
+            {
+              "Name": "method",
+              "Type": "System.IntPtr"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.StatusCodes",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Field",
+          "Name": "Status100Continue",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "100"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status101SwitchingProtocols",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "101"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status102Processing",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "102"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status200OK",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "200"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status201Created",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "201"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status202Accepted",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "202"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status203NonAuthoritative",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "203"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status204NoContent",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "204"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status205ResetContent",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "205"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status206PartialContent",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "206"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status207MultiStatus",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "207"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status208AlreadyReported",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "208"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status226IMUsed",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "226"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status300MultipleChoices",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "300"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status301MovedPermanently",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "301"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status302Found",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "302"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status303SeeOther",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "303"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status304NotModified",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "304"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status305UseProxy",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "305"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status306SwitchProxy",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "306"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status307TemporaryRedirect",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "307"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status308PermanentRedirect",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "308"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status400BadRequest",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "400"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status401Unauthorized",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "401"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status402PaymentRequired",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "402"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status403Forbidden",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "403"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status404NotFound",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "404"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status405MethodNotAllowed",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "405"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status406NotAcceptable",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "406"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status407ProxyAuthenticationRequired",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "407"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status408RequestTimeout",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "408"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status409Conflict",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "409"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status410Gone",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "410"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status411LengthRequired",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "411"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status412PreconditionFailed",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "412"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status413RequestEntityTooLarge",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "413"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status413PayloadTooLarge",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "413"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status414RequestUriTooLong",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "414"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status414UriTooLong",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "414"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status415UnsupportedMediaType",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "415"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status416RequestedRangeNotSatisfiable",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "416"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status416RangeNotSatisfiable",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "416"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status417ExpectationFailed",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "417"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status418ImATeapot",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "418"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status419AuthenticationTimeout",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "419"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status421MisdirectedRequest",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "421"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status422UnprocessableEntity",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "422"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status423Locked",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "423"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status424FailedDependency",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "424"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status426UpgradeRequired",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "426"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status428PreconditionRequired",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "428"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status429TooManyRequests",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "429"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status431RequestHeaderFieldsTooLarge",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "431"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status451UnavailableForLegalReasons",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "451"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status500InternalServerError",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "500"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status501NotImplemented",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "501"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status502BadGateway",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "502"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status503ServiceUnavailable",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "503"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status504GatewayTimeout",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "504"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status505HttpVersionNotsupported",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "505"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status506VariantAlsoNegotiates",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "506"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status507InsufficientStorage",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "507"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status508LoopDetected",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "508"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status510NotExtended",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "510"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Status511NetworkAuthenticationRequired",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "511"
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.WebSocketManager",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_IsWebSocketRequest",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_WebSocketRequestedProtocols",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IList<System.String>",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AcceptWebSocketAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<System.Net.WebSockets.WebSocket>",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AcceptWebSocketAsync",
+          "Parameters": [
+            {
+              "Name": "subProtocol",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Net.WebSockets.WebSocket>",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Protected",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Authentication.AuthenticateInfo",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Principal",
+          "Parameters": [],
+          "ReturnType": "System.Security.Claims.ClaimsPrincipal",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Principal",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Properties",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Properties",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Description",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.Authentication.AuthenticationDescription",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Description",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.Authentication.AuthenticationDescription"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Authentication.AuthenticationDescription",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Items",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.String, System.Object>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_AuthenticationScheme",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_AuthenticationScheme",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_DisplayName",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_DisplayName",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "items",
+              "Type": "System.Collections.Generic.IDictionary<System.String, System.Object>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Authentication.AuthenticationManager",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_HttpContext",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.HttpContext",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetAuthenticationSchemes",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IEnumerable<Microsoft.AspNetCore.Http.Authentication.AuthenticationDescription>",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetAuthenticateInfoAsync",
+          "Parameters": [
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Http.Authentication.AuthenticateInfo>",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AuthenticateAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.Features.Authentication.AuthenticateContext"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AuthenticateAsync",
+          "Parameters": [
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Security.Claims.ClaimsPrincipal>",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ChallengeAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ChallengeAsync",
+          "Parameters": [
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ChallengeAsync",
+          "Parameters": [
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ChallengeAsync",
+          "Parameters": [
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignInAsync",
+          "Parameters": [
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "principal",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ForbidAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ForbidAsync",
+          "Parameters": [
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ForbidAsync",
+          "Parameters": [
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ForbidAsync",
+          "Parameters": [
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ChallengeAsync",
+          "Parameters": [
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties"
+            },
+            {
+              "Name": "behavior",
+              "Type": "Microsoft.AspNetCore.Http.Features.Authentication.ChallengeBehavior"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignInAsync",
+          "Parameters": [
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "principal",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignOutAsync",
+          "Parameters": [
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignOutAsync",
+          "Parameters": [
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "properties",
+              "Type": "Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Abstract": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "AutomaticScheme",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "\"Automatic\""
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Authentication.AuthenticationProperties",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Items",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.String, System.String>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IsPersistent",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_IsPersistent",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_RedirectUri",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RedirectUri",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IssuedUtc",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_IssuedUtc",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.DateTimeOffset>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ExpiresUtc",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ExpiresUtc",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.DateTimeOffset>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_AllowRefresh",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Boolean>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_AllowRefresh",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.Boolean>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "items",
+              "Type": "System.Collections.Generic.IDictionary<System.String, System.String>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/Http/Http.Abstractions/test/CookieBuilderTests.cs b/src/Http/Http.Abstractions/test/CookieBuilderTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..dd540ccc1ba012ac2d8d7083e0f9e66267644da7
--- /dev/null
+++ b/src/Http/Http.Abstractions/test/CookieBuilderTests.cs
@@ -0,0 +1,57 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Abstractions.Tests
+{
+    public class CookieBuilderTests
+    {
+        [Theory]
+        [InlineData(CookieSecurePolicy.Always, false, true)]
+        [InlineData(CookieSecurePolicy.Always, true, true)]
+        [InlineData(CookieSecurePolicy.SameAsRequest, true, true)]
+        [InlineData(CookieSecurePolicy.SameAsRequest, false, false)]
+        [InlineData(CookieSecurePolicy.None, true, false)]
+        [InlineData(CookieSecurePolicy.None, false, false)]
+        public void ConfiguresSecurePolicy(CookieSecurePolicy policy, bool requestIsHttps, bool secure)
+        {
+            var builder = new CookieBuilder
+            {
+                SecurePolicy = policy
+            };
+            var context = new DefaultHttpContext();
+            context.Request.IsHttps = requestIsHttps;
+            var options = builder.Build(context);
+
+            Assert.Equal(secure, options.Secure);
+        }
+
+        [Fact]
+        public void ComputesExpiration()
+        {
+            Assert.Null(new CookieBuilder().Build(new DefaultHttpContext()).Expires);
+
+            var now = DateTimeOffset.Now;
+            var options = new CookieBuilder { Expiration = TimeSpan.FromHours(1) }.Build(new DefaultHttpContext(), now);
+            Assert.Equal(now.AddHours(1), options.Expires);
+        }
+
+        [Fact]
+        public void ComputesMaxAge()
+        {
+            Assert.Null(new CookieBuilder().Build(new DefaultHttpContext()).MaxAge);
+
+            var now = TimeSpan.FromHours(1);
+            var options = new CookieBuilder { MaxAge = now }.Build(new DefaultHttpContext());
+            Assert.Equal(now, options.MaxAge);
+        }
+
+        [Fact]
+        public void CookieBuilderPreservesDefaultPath()
+        {
+            Assert.Equal(new CookieOptions().Path, new CookieBuilder().Build(new DefaultHttpContext()).Path);
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/test/FragmentStringTests.cs b/src/Http/Http.Abstractions/test/FragmentStringTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4f5fe20916ca5370d1914b2f94ed48c350dfcb0d
--- /dev/null
+++ b/src/Http/Http.Abstractions/test/FragmentStringTests.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 Xunit;
+
+namespace Microsoft.AspNetCore.Http.Abstractions.Tests
+{
+    public class FragmentStringTests
+    {
+        [Fact]
+        public void Equals_EmptyFragmentStringAndDefaultFragmentString()
+        {
+            // Act and Assert
+            Assert.Equal(default(FragmentString), FragmentString.Empty);
+            Assert.Equal(default(FragmentString), FragmentString.Empty);
+            // explicitly checking == operator
+            Assert.True(FragmentString.Empty == default(FragmentString));
+            Assert.True(default(FragmentString) == FragmentString.Empty);
+        }
+
+        [Fact]
+        public void NotEquals_DefaultFragmentStringAndNonNullFragmentString()
+        {
+            // Arrange
+            var fragmentString = new FragmentString("#col=1");
+
+            // Act and Assert
+            Assert.NotEqual(default(FragmentString), fragmentString);
+        }
+
+        [Fact]
+        public void NotEquals_EmptyFragmentStringAndNonNullFragmentString()
+        {
+            // Arrange
+            var fragmentString = new FragmentString("#col=1");
+
+            // Act and Assert
+            Assert.NotEqual(fragmentString, FragmentString.Empty);
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/test/HostStringTest.cs b/src/Http/Http.Abstractions/test/HostStringTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..85820f8ffcae1d76e55116adf964dcdc8abeb1c7
--- /dev/null
+++ b/src/Http/Http.Abstractions/test/HostStringTest.cs
@@ -0,0 +1,175 @@
+// 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.Testing;
+using Microsoft.Extensions.Primitives;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public class HostStringTests
+    {
+        [Theory]
+        [InlineData(0)]
+        [InlineData(-1)]
+        public void CtorThrows_IfPortIsNotGreaterThanZero(int port)
+        {
+            // Act and Assert
+            ExceptionAssert.ThrowsArgumentOutOfRange(() => new HostString("localhost", port), "port", "The value must be greater than zero.");
+        }
+
+        [Theory]
+        [InlineData("localhost", "localhost")]
+        [InlineData("1.2.3.4", "1.2.3.4")]
+        [InlineData("[2001:db8:a0b:12f0::1]", "[2001:db8:a0b:12f0::1]")]
+        [InlineData("本地主機", "本地主機")]
+        [InlineData("localhost:5000", "localhost")]
+        [InlineData("1.2.3.4:5000", "1.2.3.4")]
+        [InlineData("[2001:db8:a0b:12f0::1]:5000", "[2001:db8:a0b:12f0::1]")]
+        [InlineData("本地主機:5000", "本地主機")]
+        public void Domain_ExtractsHostFromValue(string sourceValue, string expectedDomain)
+        {
+            // Arrange
+            var hostString = new HostString(sourceValue);
+
+            // Act
+            var result = hostString.Host;
+
+            // Assert
+            Assert.Equal(expectedDomain, result);
+        }
+
+        [Theory]
+        [InlineData("localhost", null)]
+        [InlineData("1.2.3.4", null)]
+        [InlineData("[2001:db8:a0b:12f0::1]", null)]
+        [InlineData("本地主機", null)]
+        [InlineData("localhost:5000", 5000)]
+        [InlineData("1.2.3.4:5000", 5000)]
+        [InlineData("[2001:db8:a0b:12f0::1]:5000", 5000)]
+        [InlineData("本地主機:5000", 5000)]
+        public void Port_ExtractsPortFromValue(string sourceValue, int? expectedPort)
+        {
+            // Arrange
+            var hostString = new HostString(sourceValue);
+
+            // Act
+            var result = hostString.Port;
+
+            // Assert
+            Assert.Equal(expectedPort, result);
+        }
+
+        [Theory]
+        [InlineData("localhost:BLAH")]
+        public void Port_ExtractsInvalidPortFromValue(string sourceValue)
+        {
+            // Arrange
+            var hostString = new HostString(sourceValue);
+
+            // Act
+            var result = hostString.Port;
+
+            // Assert
+            Assert.Null(result);
+        }
+
+        [Theory]
+        [InlineData("localhost", 5000, "localhost", 5000)]
+        [InlineData("1.2.3.4", 5000, "1.2.3.4", 5000)]
+        [InlineData("[2001:db8:a0b:12f0::1]", 5000, "[2001:db8:a0b:12f0::1]", 5000)]
+        [InlineData("2001:db8:a0b:12f0::1", 5000, "[2001:db8:a0b:12f0::1]", 5000)]
+        [InlineData("本地主機", 5000, "本地主機", 5000)]
+        public void Ctor_CreatesFromHostAndPort(string sourceHost, int sourcePort, string expectedHost, int expectedPort)
+        {
+            // Arrange
+            var hostString = new HostString(sourceHost, sourcePort);
+
+            // Act
+            var host = hostString.Host;
+            var port = hostString.Port;
+
+            // Assert
+            Assert.Equal(expectedHost, host);
+            Assert.Equal(expectedPort, port);
+        }
+
+        [Fact]
+        public void Equals_EmptyHostStringAndDefaultHostString()
+        {
+            // Act and Assert
+            Assert.Equal(default(HostString), new HostString(string.Empty));
+            Assert.Equal(default(HostString), new HostString(string.Empty));
+            // explicitly checking == operator
+            Assert.True(new HostString(string.Empty) == default(HostString));
+            Assert.True(default(HostString) == new HostString(string.Empty));
+        }
+
+        [Fact]
+        public void NotEquals_DefaultHostStringAndNonNullHostString()
+        {
+            // Arrange
+            var hostString = new HostString("example.com");
+
+            // Act and Assert
+            Assert.NotEqual(default(HostString), hostString);
+        }
+
+        [Fact]
+        public void NotEquals_EmptyHostStringAndNonNullHostString()
+        {
+            // Arrange
+            var hostString = new HostString("example.com");
+
+            // Act and Assert
+            Assert.NotEqual(hostString, new HostString(string.Empty));
+        }
+
+        [Theory]
+        [InlineData("localHost", "localhost")]
+        [InlineData("localHost", "*")] // Any - Used by HttpSys
+        [InlineData("localhost:9090", "localHost")]
+        [InlineData("example.com:443", "example.com")]
+        [InlineData("foo.eXample.com:443", "*.exampLe.com")]
+        [InlineData("f.eXample.com:443", "*.exampLe.com")]
+        [InlineData("a.b.c.eXample.com:443", "*.exampLe.com")]
+        [InlineData("127.0.0.1", "127.0.0.1")]
+        [InlineData("127.0.0.1:443", "127.0.0.1")]
+        [InlineData("xn--c1yn36f:443", "xn--c1yn36f")]
+        [InlineData("點看", "點看")]
+        [InlineData("[::ABC]", "[::aBc]")]
+        [InlineData("[::1]:80", "[::1]")]
+        [InlineData("[::1]:", "[::1]")]
+        [InlineData("::1", "[::1]")]
+        public void HostMatches(string host, string pattern)
+        {
+            Assert.True(HostString.MatchesAny(host, new StringSegment[] { pattern }));
+        }
+
+        [Theory]
+        [InlineData("example.com", "localhost")]
+        [InlineData("localhost:9090", "example.com")]
+        [InlineData(":80", "localhost")]
+        [InlineData(":", "localhost")]
+        [InlineData("example.com:443", "*.example.com")]
+        [InlineData(".example.com:443", "*.example.com")]
+        [InlineData("foo.com:443", "*.example.com")]
+        [InlineData("foo.example.com.bar:443", "*.example.com")]
+        [InlineData(".com:443", "*.com")]
+        [InlineData("xn--c1yn36f:443", "點看")]
+        [InlineData("[::1", "[::1]")]
+        [InlineData("[::1:80", "[::1]")]
+        [InlineData("::1", "::1")] // Brackets are added to the host before the comparison
+        public void HostDoesntMatch(string host, string pattern)
+        {
+            Assert.False(HostString.MatchesAny(host, new StringSegment[] { pattern }));
+        }
+
+        [Fact]
+        public void HostMatchThrowsForBadPort()
+        {
+            Assert.Throws<FormatException>(() => HostString.MatchesAny("example.com:1abc", new StringSegment[] { "example.com" }));
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/test/HttpResponseWritingExtensionsTests.cs b/src/Http/Http.Abstractions/test/HttpResponseWritingExtensionsTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f8e9e27d1ce9a0ba14771de3505268e58303f9f8
--- /dev/null
+++ b/src/Http/Http.Abstractions/test/HttpResponseWritingExtensionsTests.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.IO;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public class HttpResponseWritingExtensionsTests
+    {
+        [Fact]
+        public async Task WritingText_WriteText()
+        {
+            HttpContext context = CreateRequest();
+            await context.Response.WriteAsync("Hello World");
+
+            Assert.Equal(11, context.Response.Body.Length);
+        }
+
+        [Fact]
+        public async Task WritingText_MultipleWrites()
+        {
+            HttpContext context = CreateRequest();
+            await context.Response.WriteAsync("Hello World");
+            await context.Response.WriteAsync("Hello World");
+
+            Assert.Equal(22, context.Response.Body.Length);
+        }
+
+        private HttpContext CreateRequest()
+        {
+            HttpContext context = new DefaultHttpContext();
+            context.Response.Body = new MemoryStream();
+            return context;
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/test/MapPathMiddlewareTests.cs b/src/Http/Http.Abstractions/test/MapPathMiddlewareTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a30e99603ca1952668b0fc08de369e0d94afc8c1
--- /dev/null
+++ b/src/Http/Http.Abstractions/test/MapPathMiddlewareTests.cs
@@ -0,0 +1,199 @@
+// 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.Builder.Internal;
+using Microsoft.AspNetCore.Http;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Builder.Extensions
+{
+    public class MapPathMiddlewareTests
+    {
+        private static readonly Action<IApplicationBuilder> ActionNotImplemented = new Action<IApplicationBuilder>(_ => { throw new NotImplementedException(); });
+
+        private static Task Success(HttpContext context)
+        {
+            context.Response.StatusCode = 200;
+            context.Items["test.PathBase"] = context.Request.PathBase.Value;
+            context.Items["test.Path"] = context.Request.Path.Value;
+            return Task.FromResult<object>(null);
+        }
+
+        private static void UseSuccess(IApplicationBuilder app)
+        {
+            app.Run(Success);
+        }
+
+        private static Task NotImplemented(HttpContext context)
+        {
+            throw new NotImplementedException();
+        }
+
+        private static void UseNotImplemented(IApplicationBuilder app)
+        {
+            app.Run(NotImplemented);
+        }
+
+        [Fact]
+        public void NullArguments_ArgumentNullException()
+        {
+            var builder = new ApplicationBuilder(serviceProvider: null);
+            var noMiddleware = new ApplicationBuilder(serviceProvider: null).Build();
+            var noOptions = new MapOptions();
+            Assert.Throws<ArgumentNullException>(() => builder.Map("/foo", configuration: null));
+            Assert.Throws<ArgumentNullException>(() => new MapMiddleware(noMiddleware, null));
+        }
+
+        [Theory]
+        [InlineData("/foo", "", "/foo")]
+        [InlineData("/foo", "", "/foo/")]
+        [InlineData("/foo", "/Bar", "/foo")]
+        [InlineData("/foo", "/Bar", "/foo/cho")]
+        [InlineData("/foo", "/Bar", "/foo/cho/")]
+        [InlineData("/foo/cho", "/Bar", "/foo/cho")]
+        [InlineData("/foo/cho", "/Bar", "/foo/cho/do")]
+        public void PathMatchFunc_BranchTaken(string matchPath, string basePath, string requestPath)
+        {
+            HttpContext context = CreateRequest(basePath, requestPath);
+            var builder = new ApplicationBuilder(serviceProvider: null);
+            builder.Map(matchPath, UseSuccess);
+            var app = builder.Build();
+            app.Invoke(context).Wait();
+
+            Assert.Equal(200, context.Response.StatusCode);
+            Assert.Equal(basePath, context.Request.PathBase.Value);
+            Assert.Equal(requestPath, context.Request.Path.Value);
+        }
+
+        [Theory]
+        [InlineData("/foo", "", "/foo")]
+        [InlineData("/foo", "", "/foo/")]
+        [InlineData("/foo", "/Bar", "/foo")]
+        [InlineData("/foo", "/Bar", "/foo/cho")]
+        [InlineData("/foo", "/Bar", "/foo/cho/")]
+        [InlineData("/foo/cho", "/Bar", "/foo/cho")]
+        [InlineData("/foo/cho", "/Bar", "/foo/cho/do")]
+        [InlineData("/foo", "", "/Foo")]
+        [InlineData("/foo", "", "/Foo/")]
+        [InlineData("/foo", "/Bar", "/Foo")]
+        [InlineData("/foo", "/Bar", "/Foo/Cho")]
+        [InlineData("/foo", "/Bar", "/Foo/Cho/")]
+        [InlineData("/foo/cho", "/Bar", "/Foo/Cho")]
+        [InlineData("/foo/cho", "/Bar", "/Foo/Cho/do")]
+        public void PathMatchAction_BranchTaken(string matchPath, string basePath, string requestPath)
+        {
+            HttpContext context = CreateRequest(basePath, requestPath);
+            var builder = new ApplicationBuilder(serviceProvider: null);
+            builder.Map(matchPath, subBuilder => subBuilder.Run(Success));
+            var app = builder.Build();
+            app.Invoke(context).Wait();
+
+            Assert.Equal(200, context.Response.StatusCode);
+            Assert.Equal(basePath + requestPath.Substring(0, matchPath.Length), (string)context.Items["test.PathBase"]);
+            Assert.Equal(requestPath.Substring(matchPath.Length), context.Items["test.Path"]);
+        }
+
+        [Theory]
+        [InlineData("/")]
+        [InlineData("/foo/")]
+        [InlineData("/foo/cho/")]
+        public void MatchPathWithTrailingSlashThrowsException(string matchPath)
+        {
+            Assert.Throws<ArgumentException>(() => new ApplicationBuilder(serviceProvider: null).Map(matchPath, map => { }).Build());
+        }
+
+        [Theory]
+        [InlineData("/foo", "", "")]
+        [InlineData("/foo", "/bar", "")]
+        [InlineData("/foo", "", "/bar")]
+        [InlineData("/foo", "/foo", "")]
+        [InlineData("/foo", "/foo", "/bar")]
+        [InlineData("/foo", "", "/bar/foo")]
+        [InlineData("/foo/bar", "/foo", "/bar")]
+        public void PathMismatchFunc_PassedThrough(string matchPath, string basePath, string requestPath)
+        {
+            HttpContext context = CreateRequest(basePath, requestPath);
+            var builder = new ApplicationBuilder(serviceProvider: null);
+            builder.Map(matchPath, UseNotImplemented);
+            builder.Run(Success);
+            var app = builder.Build();
+            app.Invoke(context).Wait();
+
+            Assert.Equal(200, context.Response.StatusCode);
+            Assert.Equal(basePath, context.Request.PathBase.Value);
+            Assert.Equal(requestPath, context.Request.Path.Value);
+        }
+
+        [Theory]
+        [InlineData("/foo", "", "")]
+        [InlineData("/foo", "/bar", "")]
+        [InlineData("/foo", "", "/bar")]
+        [InlineData("/foo", "/foo", "")]
+        [InlineData("/foo", "/foo", "/bar")]
+        [InlineData("/foo", "", "/bar/foo")]
+        [InlineData("/foo/bar", "/foo", "/bar")]
+        public void PathMismatchAction_PassedThrough(string matchPath, string basePath, string requestPath)
+        {
+            HttpContext context = CreateRequest(basePath, requestPath);
+            var builder = new ApplicationBuilder(serviceProvider: null);
+            builder.Map(matchPath, UseNotImplemented);
+            builder.Run(Success);
+            var app = builder.Build();
+            app.Invoke(context).Wait();
+
+            Assert.Equal(200, context.Response.StatusCode);
+            Assert.Equal(basePath, context.Request.PathBase.Value);
+            Assert.Equal(requestPath, context.Request.Path.Value);
+        }
+
+        [Fact]
+        public void ChainedRoutes_Success()
+        {
+            var builder = new ApplicationBuilder(serviceProvider: null);
+            builder.Map("/route1", map =>
+            {
+                map.Map("/subroute1", UseSuccess);
+                map.Run(NotImplemented);
+            });
+            builder.Map("/route2/subroute2", UseSuccess);
+            var app = builder.Build();
+
+            HttpContext context = CreateRequest(string.Empty, "/route1");
+            Assert.Throws<AggregateException>(() => app.Invoke(context).Wait());
+
+            context = CreateRequest(string.Empty, "/route1/subroute1");
+            app.Invoke(context).Wait();
+            Assert.Equal(200, context.Response.StatusCode);
+            Assert.Equal(string.Empty, context.Request.PathBase.Value);
+            Assert.Equal("/route1/subroute1", context.Request.Path.Value);
+
+            context = CreateRequest(string.Empty, "/route2");
+            app.Invoke(context).Wait();
+            Assert.Equal(404, context.Response.StatusCode);
+            Assert.Equal(string.Empty, context.Request.PathBase.Value);
+            Assert.Equal("/route2", context.Request.Path.Value);
+
+            context = CreateRequest(string.Empty, "/route2/subroute2");
+            app.Invoke(context).Wait();
+            Assert.Equal(200, context.Response.StatusCode);
+            Assert.Equal(string.Empty, context.Request.PathBase.Value);
+            Assert.Equal("/route2/subroute2", context.Request.Path.Value);
+
+            context = CreateRequest(string.Empty, "/route2/subroute2/subsub2");
+            app.Invoke(context).Wait();
+            Assert.Equal(200, context.Response.StatusCode);
+            Assert.Equal(string.Empty, context.Request.PathBase.Value);
+            Assert.Equal("/route2/subroute2/subsub2", context.Request.Path.Value);
+        }
+
+        private HttpContext CreateRequest(string basePath, string requestPath)
+        {
+            HttpContext context = new DefaultHttpContext();
+            context.Request.PathBase = new PathString(basePath);
+            context.Request.Path = new PathString(requestPath);
+            return context;
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/test/MapPredicateMiddlewareTests.cs b/src/Http/Http.Abstractions/test/MapPredicateMiddlewareTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0313a730d51c83a7d47ed23dc2bd91a6831f095f
--- /dev/null
+++ b/src/Http/Http.Abstractions/test/MapPredicateMiddlewareTests.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.Threading.Tasks;
+using Microsoft.AspNetCore.Builder.Internal;
+using Microsoft.AspNetCore.Http;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Builder.Extensions
+{
+    using Predicate = Func<HttpContext, bool>;
+
+    public class MapPredicateMiddlewareTests
+    {
+        private static readonly Predicate NotImplementedPredicate = new Predicate(envionment => { throw new NotImplementedException(); });
+
+        private static Task Success(HttpContext context)
+        {
+            context.Response.StatusCode = 200;
+            return Task.FromResult<object>(null);
+        }
+
+        private static void UseSuccess(IApplicationBuilder app)
+        {
+            app.Run(Success);
+        }
+
+        private static Task NotImplemented(HttpContext context)
+        {
+            throw new NotImplementedException();
+        }
+
+        private static void UseNotImplemented(IApplicationBuilder app)
+        {
+            app.Run(NotImplemented);
+        }
+
+        private bool TruePredicate(HttpContext context)
+        {
+            return true;
+        }
+
+        private bool FalsePredicate(HttpContext context)
+        {
+            return false;
+        }
+
+        [Fact]
+        public void NullArguments_ArgumentNullException()
+        {
+            var builder = new ApplicationBuilder(serviceProvider: null);
+            var noMiddleware = new ApplicationBuilder(serviceProvider: null).Build();
+            var noOptions = new MapWhenOptions();
+            Assert.Throws<ArgumentNullException>(() => builder.MapWhen(null, UseNotImplemented));
+            Assert.Throws<ArgumentNullException>(() => builder.MapWhen(NotImplementedPredicate, configuration: null));
+            Assert.Throws<ArgumentNullException>(() => new MapWhenMiddleware(null, noOptions));
+            Assert.Throws<ArgumentNullException>(() => new MapWhenMiddleware(noMiddleware, null));
+            Assert.Throws<ArgumentNullException>(() => new MapWhenMiddleware(null, noOptions));
+            Assert.Throws<ArgumentNullException>(() => new MapWhenMiddleware(noMiddleware, null));
+        }
+
+        [Fact]
+        public void PredicateTrue_BranchTaken()
+        {
+            HttpContext context = CreateRequest();
+            var builder = new ApplicationBuilder(serviceProvider: null);
+            builder.MapWhen(TruePredicate, UseSuccess);
+            var app = builder.Build();
+            app.Invoke(context).Wait();
+
+            Assert.Equal(200, context.Response.StatusCode);
+        }
+
+        [Fact]
+        public void PredicateTrueAction_BranchTaken()
+        {
+            HttpContext context = CreateRequest();
+            var builder = new ApplicationBuilder(serviceProvider: null);
+            builder.MapWhen(TruePredicate, UseSuccess);
+            var app = builder.Build();
+            app.Invoke(context).Wait();
+
+            Assert.Equal(200, context.Response.StatusCode);
+        }
+
+        [Fact]
+        public void PredicateFalseAction_PassThrough()
+        {
+            HttpContext context = CreateRequest();
+            var builder = new ApplicationBuilder(serviceProvider: null);
+            builder.MapWhen(FalsePredicate, UseNotImplemented);
+            builder.Run(Success);
+            var app = builder.Build();
+            app.Invoke(context).Wait();
+
+            Assert.Equal(200, context.Response.StatusCode);
+        }
+
+        [Fact]
+        public void ChainedPredicates_Success()
+        {
+            var builder = new ApplicationBuilder(serviceProvider: null);
+            builder.MapWhen(TruePredicate, map1 =>
+            {
+                map1.MapWhen((Predicate)FalsePredicate, UseNotImplemented);
+                map1.MapWhen((Predicate)TruePredicate, map2 => map2.MapWhen((Predicate)TruePredicate, UseSuccess));
+                map1.Run(NotImplemented);
+            });
+            var app = builder.Build();
+
+            HttpContext context = CreateRequest();
+            app.Invoke(context).Wait();
+            Assert.Equal(200, context.Response.StatusCode);
+        }
+
+        private HttpContext CreateRequest()
+        {
+            HttpContext context = new DefaultHttpContext();
+            return context;
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/test/Microsoft.AspNetCore.Http.Abstractions.Tests.csproj b/src/Http/Http.Abstractions/test/Microsoft.AspNetCore.Http.Abstractions.Tests.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..a97c164925e7595210b08dcd691551de55a5de34
--- /dev/null
+++ b/src/Http/Http.Abstractions/test/Microsoft.AspNetCore.Http.Abstractions.Tests.csproj
@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Http" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/Http.Abstractions/test/PathStringTests.cs b/src/Http/Http.Abstractions/test/PathStringTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2d3c6e23f09088c837c5403fed35a6d452d402d4
--- /dev/null
+++ b/src/Http/Http.Abstractions/test/PathStringTests.cs
@@ -0,0 +1,240 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.ComponentModel;
+using Microsoft.AspNetCore.Testing;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public class PathStringTests
+    {
+        [Fact]
+        public void CtorThrows_IfPathDoesNotHaveLeadingSlash()
+        {
+            // Act and Assert
+            ExceptionAssert.ThrowsArgument(() => new PathString("hello"), "value", "The path in 'value' must start with '/'.");
+        }
+
+        [Fact]
+        public void Equals_EmptyPathStringAndDefaultPathString()
+        {
+            // Act and Assert
+            Assert.Equal(default(PathString), PathString.Empty);
+            Assert.Equal(default(PathString), PathString.Empty);
+            Assert.True(PathString.Empty == default(PathString));
+            Assert.True(default(PathString) == PathString.Empty);
+            Assert.True(PathString.Empty.Equals(default(PathString)));
+            Assert.True(default(PathString).Equals(PathString.Empty));
+        }
+
+        [Fact]
+        public void NotEquals_DefaultPathStringAndNonNullPathString()
+        {
+            // Arrange
+            var pathString = new PathString("/hello");
+
+            // Act and Assert
+            Assert.NotEqual(default(PathString), pathString);
+        }
+
+        [Fact]
+        public void NotEquals_EmptyPathStringAndNonNullPathString()
+        {
+            // Arrange
+            var pathString = new PathString("/hello");
+
+            // Act and Assert
+            Assert.NotEqual(pathString, PathString.Empty);
+        }
+
+        [Fact]
+        public void HashCode_CheckNullAndEmptyHaveSameHashcodes()
+        {
+            Assert.Equal(PathString.Empty.GetHashCode(), default(PathString).GetHashCode());
+        }
+
+        [Theory]
+        [InlineData(null, null)]
+        [InlineData("", null)]
+        public void AddPathString_HandlesNullAndEmptyStrings(string appString, string concatString)
+        {
+            // Arrange
+            var appPath = new PathString(appString);
+            var concatPath = new PathString(concatString);
+
+            // Act
+            var result = appPath.Add(concatPath);
+
+            // Assert
+            Assert.False(result.HasValue);
+        }
+
+        [Theory]
+        [InlineData("", "/", "/")]
+        [InlineData("/", null, "/")]
+        [InlineData("/", "", "/")]
+        [InlineData("/", "/test", "/test")]
+        [InlineData("/myapp/", "/test/bar", "/myapp/test/bar")]
+        [InlineData("/myapp/", "/test/bar/", "/myapp/test/bar/")]
+        public void AddPathString_HandlesLeadingAndTrailingSlashes(string appString, string concatString, string expected)
+        {
+            // Arrange
+            var appPath = new PathString(appString);
+            var concatPath = new PathString(concatString);
+
+            // Act
+            var result = appPath.Add(concatPath);
+
+            // Assert
+            Assert.Equal(expected, result.Value);
+        }
+
+        [Fact]
+        public void ImplicitStringConverters_WorksWithAdd()
+        {
+            var scheme = "http";
+            var host = new HostString("localhost:80");
+            var pathBase = new PathString("/base");
+            var path = new PathString("/path");
+            var query = new QueryString("?query");
+            var fragment = new FragmentString("#frag");
+
+            var result = scheme + "://" + host + pathBase + path + query + fragment;
+            Assert.Equal("http://localhost:80/base/path?query#frag", result);
+
+            result = pathBase + path + query + fragment;
+            Assert.Equal("/base/path?query#frag", result);
+
+            result = path + "text";
+            Assert.Equal("/pathtext", result);
+        }
+
+        [Theory]
+        [InlineData("/test/path", "/TEST", true)]
+        [InlineData("/test/path", "/TEST/pa", false)]
+        [InlineData("/TEST/PATH", "/test", true)]
+        [InlineData("/TEST/path", "/test/pa", false)]
+        [InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", true)]
+        public void StartsWithSegments_DoesACaseInsensitiveMatch(string sourcePath, string testPath, bool expectedResult)
+        {
+            var source = new PathString(sourcePath);
+            var test = new PathString(testPath);
+
+            var result = source.StartsWithSegments(test);
+
+            Assert.Equal(expectedResult, result);
+        }
+
+        [Theory]
+        [InlineData("/test/path", "/TEST", true)]
+        [InlineData("/test/path", "/TEST/pa", false)]
+        [InlineData("/TEST/PATH", "/test", true)]
+        [InlineData("/TEST/path", "/test/pa", false)]
+        [InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", true)]
+        public void StartsWithSegmentsWithRemainder_DoesACaseInsensitiveMatch(string sourcePath, string testPath, bool expectedResult)
+        {
+            var source = new PathString(sourcePath);
+            var test = new PathString(testPath);
+
+            var result = source.StartsWithSegments(test, out var remaining);
+
+            Assert.Equal(expectedResult, result);
+        }
+
+        [Theory]
+        [InlineData("/test/path", "/TEST", StringComparison.OrdinalIgnoreCase, true)]
+        [InlineData("/test/path", "/TEST", StringComparison.Ordinal, false)]
+        [InlineData("/test/path", "/TEST/pa", StringComparison.OrdinalIgnoreCase, false)]
+        [InlineData("/test/path", "/TEST/pa", StringComparison.Ordinal, false)]
+        [InlineData("/TEST/PATH", "/test", StringComparison.OrdinalIgnoreCase, true)]
+        [InlineData("/TEST/PATH", "/test", StringComparison.Ordinal, false)]
+        [InlineData("/TEST/path", "/test/pa", StringComparison.OrdinalIgnoreCase, false)]
+        [InlineData("/TEST/path", "/test/pa", StringComparison.Ordinal, false)]
+        [InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", StringComparison.OrdinalIgnoreCase, true)]
+        [InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", StringComparison.Ordinal, false)]
+        public void StartsWithSegments_DoesMatchUsingSpecifiedComparison(string sourcePath, string testPath, StringComparison comparison, bool expectedResult)
+        {
+            var source = new PathString(sourcePath);
+            var test = new PathString(testPath);
+
+            var result = source.StartsWithSegments(test, comparison);
+
+            Assert.Equal(expectedResult, result);
+        }
+
+        [Theory]
+        [InlineData("/test/path", "/TEST", StringComparison.OrdinalIgnoreCase, true)]
+        [InlineData("/test/path", "/TEST", StringComparison.Ordinal, false)]
+        [InlineData("/test/path", "/TEST/pa", StringComparison.OrdinalIgnoreCase, false)]
+        [InlineData("/test/path", "/TEST/pa", StringComparison.Ordinal, false)]
+        [InlineData("/TEST/PATH", "/test", StringComparison.OrdinalIgnoreCase, true)]
+        [InlineData("/TEST/PATH", "/test", StringComparison.Ordinal, false)]
+        [InlineData("/TEST/path", "/test/pa", StringComparison.OrdinalIgnoreCase, false)]
+        [InlineData("/TEST/path", "/test/pa", StringComparison.Ordinal, false)]
+        [InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", StringComparison.OrdinalIgnoreCase, true)]
+        [InlineData("/test/PATH/path/TEST", "/TEST/path/PATH", StringComparison.Ordinal, false)]
+        public void StartsWithSegmentsWithRemainder_DoesMatchUsingSpecifiedComparison(string sourcePath, string testPath, StringComparison comparison, bool expectedResult)
+        {
+            var source = new PathString(sourcePath);
+            var test = new PathString(testPath);
+
+            var result = source.StartsWithSegments(test, comparison, out var remaining);
+
+            Assert.Equal(expectedResult, result);
+        }
+
+        [Theory]
+        // unreserved
+        [InlineData("/abc123.-_~", "/abc123.-_~")]
+        // colon
+        [InlineData("/:", "/:")]
+        // at
+        [InlineData("/@", "/@")]
+        // sub-delims
+        [InlineData("/!$&'()*+,;=", "/!$&'()*+,;=")]
+        // reserved
+        [InlineData("/?#[]", "/%3F%23%5B%5D")]
+        // pct-encoding
+        [InlineData("/单行道", "/%E5%8D%95%E8%A1%8C%E9%81%93")]
+        // mixed
+        [InlineData("/index/单行道=(x*y)[abc]", "/index/%E5%8D%95%E8%A1%8C%E9%81%93=(x*y)%5Babc%5D")]
+        [InlineData("/index/单行道=(x*y)[abc]_", "/index/%E5%8D%95%E8%A1%8C%E9%81%93=(x*y)%5Babc%5D_")]
+        // encoded
+        [InlineData("/http%3a%2f%2f[foo]%3A5000/", "/http%3a%2f%2f%5Bfoo%5D%3A5000/")]
+        [InlineData("/http%3a%2f%2f[foo]%3A5000/%", "/http%3a%2f%2f%5Bfoo%5D%3A5000/%25")]
+        [InlineData("/http%3a%2f%2f[foo]%3A5000/%2", "/http%3a%2f%2f%5Bfoo%5D%3A5000/%252")]
+        [InlineData("/http%3a%2f%2f[foo]%3A5000/%2F", "/http%3a%2f%2f%5Bfoo%5D%3A5000/%2F")]
+        public void ToUriComponentEscapeCorrectly(string input, string expected)
+        {
+            var path = new PathString(input);
+
+            Assert.Equal(expected, path.ToUriComponent());
+        }
+
+        [Fact]
+        public void PathStringConvertsOnlyToAndFromString()
+        {
+            var converter = TypeDescriptor.GetConverter(typeof(PathString));
+            PathString result = (PathString)converter.ConvertFromInvariantString("/foo");
+            Assert.Equal("/foo", result.ToString());
+            Assert.Equal("/foo", converter.ConvertTo(result, typeof(string)));
+            Assert.True(converter.CanConvertFrom(typeof(string)));
+            Assert.False(converter.CanConvertFrom(typeof(int)));
+            Assert.False(converter.CanConvertFrom(typeof(bool)));
+            Assert.True(converter.CanConvertTo(typeof(string)));
+            Assert.False(converter.CanConvertTo(typeof(int)));
+            Assert.False(converter.CanConvertTo(typeof(bool)));
+        }
+
+        [Fact]
+        public void PathStringStaysEqualAfterAssignments()
+        {
+            PathString p1 = "/?";
+            string s1 = p1;
+            PathString p2 = s1;
+            Assert.Equal(p1, p2);
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/test/QueryStringTests.cs b/src/Http/Http.Abstractions/test/QueryStringTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8327f12509ac5cd6909aa5b1d729f128137b9227
--- /dev/null
+++ b/src/Http/Http.Abstractions/test/QueryStringTests.cs
@@ -0,0 +1,166 @@
+// 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.Testing;
+using Microsoft.Extensions.Primitives;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Abstractions
+{
+    public class QueryStringTests
+    {
+        [Fact]
+        public void CtorThrows_IfQueryDoesNotHaveLeadingQuestionMark()
+        {
+            // Act and Assert
+            ExceptionAssert.ThrowsArgument(() => new QueryString("hello"), "value", "The leading '?' must be included for a non-empty query.");
+        }
+
+        [Fact]
+        public void CtorNullOrEmpty_Success()
+        {
+            var query = new QueryString();
+            Assert.False(query.HasValue);
+            Assert.Null(query.Value);
+
+            query = new QueryString(null);
+            Assert.False(query.HasValue);
+            Assert.Null(query.Value);
+
+            query = new QueryString(string.Empty);
+            Assert.False(query.HasValue);
+            Assert.Equal(string.Empty, query.Value);
+        }
+
+        [Fact]
+        public void CtorJustAQuestionMark_Success()
+        {
+            var query = new QueryString("?");
+            Assert.True(query.HasValue);
+            Assert.Equal("?", query.Value);
+        }
+
+        [Fact]
+        public void ToString_EncodesHash()
+        {
+            var query = new QueryString("?Hello=Wor#ld");
+            Assert.Equal("?Hello=Wor%23ld", query.ToString());
+        }
+
+        [Theory]
+        [InlineData("name", "value", "?name=value")]
+        [InlineData("na me", "val ue", "?na%20me=val%20ue")]
+        [InlineData("name", "", "?name=")]
+        [InlineData("name", null, "?name=")]
+        [InlineData("", "value", "?=value")]
+        [InlineData("", "", "?=")]
+        [InlineData("", null, "?=")]
+        public void CreateNameValue_Success(string name, string value, string exepcted)
+        {
+            var query = QueryString.Create(name, value);
+            Assert.Equal(exepcted, query.Value);
+        }
+
+        [Fact]
+        public void CreateFromList_Success()
+        {
+            var query = QueryString.Create(new[]
+            {
+                new KeyValuePair<string, string>("key1", "value1"),
+                new KeyValuePair<string, string>("key2", "value2"),
+                new KeyValuePair<string, string>("key3", "value3"),
+                new KeyValuePair<string, string>("key4", null),
+                new KeyValuePair<string, string>("key5", "")
+            });
+            Assert.Equal("?key1=value1&key2=value2&key3=value3&key4=&key5=", query.Value);
+        }
+
+        [Fact]
+        public void CreateFromListStringValues_Success()
+        {
+            var query = QueryString.Create(new[]
+            {
+                new KeyValuePair<string, StringValues>("key1", new StringValues("value1")),
+                new KeyValuePair<string, StringValues>("key2", new StringValues("value2")),
+                new KeyValuePair<string, StringValues>("key3", new StringValues("value3")),
+                new KeyValuePair<string, StringValues>("key4", new StringValues()),
+                new KeyValuePair<string, StringValues>("key5", new StringValues("")),
+            });
+            Assert.Equal("?key1=value1&key2=value2&key3=value3&key4=&key5=", query.Value);
+        }
+
+        [Theory]
+        [InlineData(null, null, null)]
+        [InlineData("", "", "")]
+        [InlineData(null, "?name2=value2", "?name2=value2")]
+        [InlineData("", "?name2=value2", "?name2=value2")]
+        [InlineData("?", "?name2=value2", "?name2=value2")]
+        [InlineData("?name1=value1", null, "?name1=value1")]
+        [InlineData("?name1=value1", "", "?name1=value1")]
+        [InlineData("?name1=value1", "?", "?name1=value1")]
+        [InlineData("?name1=value1", "?name2=value2", "?name1=value1&name2=value2")]
+        public void AddQueryString_Success(string query1, string query2, string expected)
+        {
+            var q1 = new QueryString(query1);
+            var q2 = new QueryString(query2);
+            Assert.Equal(expected, q1.Add(q2).Value);
+            Assert.Equal(expected, (q1 + q2).Value);
+        }
+
+        [Theory]
+        [InlineData("", "", "", "?=")]
+        [InlineData("", "", null, "?=")]
+        [InlineData("?", "", "", "?=")]
+        [InlineData("?", "", null, "?=")]
+        [InlineData("?", "name2", "value2", "?name2=value2")]
+        [InlineData("?", "name2", "", "?name2=")]
+        [InlineData("?", "name2", null, "?name2=")]
+        [InlineData("?name1=value1", "name2", "value2", "?name1=value1&name2=value2")]
+        [InlineData("?name1=value1", "na me2", "val ue2", "?name1=value1&na%20me2=val%20ue2")]
+        [InlineData("?name1=value1", "", "", "?name1=value1&=")]
+        [InlineData("?name1=value1", "", null, "?name1=value1&=")]
+        [InlineData("?name1=value1", "name2", "", "?name1=value1&name2=")]
+        [InlineData("?name1=value1", "name2", null, "?name1=value1&name2=")]
+        public void AddNameValue_Success(string query1, string name2, string value2, string expected)
+        {
+            var q1 = new QueryString(query1);
+            var q2 = q1.Add(name2, value2);
+            Assert.Equal(expected, q2.Value);
+        }
+
+        [Fact]
+        public void Equals_EmptyQueryStringAndDefaultQueryString()
+        {
+            // Act and Assert
+            Assert.Equal(default(QueryString), QueryString.Empty);
+            Assert.Equal(default(QueryString), QueryString.Empty);
+            // explicitly checking == operator
+            Assert.True(QueryString.Empty == default(QueryString));
+            Assert.True(default(QueryString) == QueryString.Empty);
+        }
+
+        [Fact]
+        public void NotEquals_DefaultQueryStringAndNonNullQueryString()
+        {
+            // Arrange
+            var queryString = new QueryString("?foo=1");
+
+            // Act and Assert
+            Assert.NotEqual(default(QueryString), queryString);
+        }
+
+        [Fact]
+        public void NotEquals_EmptyQueryStringAndNonNullQueryString()
+        {
+            // Arrange
+            var queryString = new QueryString("?foo=1");
+
+            // Act and Assert
+            Assert.NotEqual(queryString, QueryString.Empty);
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/test/UseMiddlewareTest.cs b/src/Http/Http.Abstractions/test/UseMiddlewareTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..07c1aa4e8d7dc20becd07bff7f01dbebf67ddd63
--- /dev/null
+++ b/src/Http/Http.Abstractions/test/UseMiddlewareTest.cs
@@ -0,0 +1,376 @@
+// 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.Builder;
+using Microsoft.AspNetCore.Builder.Internal;
+using Microsoft.AspNetCore.Http.Abstractions;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public class UseMiddlewareTest
+    {
+        [Fact]
+        public void UseMiddleware_WithNoParameters_ThrowsException()
+        {
+            var builder = new ApplicationBuilder(new DummyServiceProvider());
+            builder.UseMiddleware(typeof(MiddlewareNoParametersStub));
+            var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
+
+            Assert.Equal(
+                Resources.FormatException_UseMiddlewareNoParameters(
+                    UseMiddlewareExtensions.InvokeMethodName,
+                    UseMiddlewareExtensions.InvokeAsyncMethodName,
+                    nameof(HttpContext)),
+                exception.Message);
+        }
+
+        [Fact]
+        public void UseMiddleware_AsyncWithNoParameters_ThrowsException()
+        {
+            var builder = new ApplicationBuilder(new DummyServiceProvider());
+            builder.UseMiddleware(typeof(MiddlewareAsyncNoParametersStub));
+            var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
+
+            Assert.Equal(
+                Resources.FormatException_UseMiddlewareNoParameters(
+                    UseMiddlewareExtensions.InvokeMethodName,
+                    UseMiddlewareExtensions.InvokeAsyncMethodName,
+                    nameof(HttpContext)),
+                exception.Message);
+        }
+
+        [Fact]
+        public void UseMiddleware_NonTaskReturnType_ThrowsException()
+        {
+            var builder = new ApplicationBuilder(new DummyServiceProvider());
+            builder.UseMiddleware(typeof(MiddlewareNonTaskReturnStub));
+            var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
+
+            Assert.Equal(
+                Resources.FormatException_UseMiddlewareNonTaskReturnType(
+                    UseMiddlewareExtensions.InvokeMethodName,
+                    UseMiddlewareExtensions.InvokeAsyncMethodName,
+                    nameof(Task)),
+                exception.Message);
+        }
+
+        [Fact]
+        public void UseMiddleware_AsyncNonTaskReturnType_ThrowsException()
+        {
+            var builder = new ApplicationBuilder(new DummyServiceProvider());
+            builder.UseMiddleware(typeof(MiddlewareAsyncNonTaskReturnStub));
+            var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
+
+            Assert.Equal(
+                Resources.FormatException_UseMiddlewareNonTaskReturnType(
+                    UseMiddlewareExtensions.InvokeMethodName,
+                    UseMiddlewareExtensions.InvokeAsyncMethodName,
+                    nameof(Task)),
+                exception.Message);
+        }
+
+        [Fact]
+        public void UseMiddleware_NoInvokeOrInvokeAsyncMethod_ThrowsException()
+        {
+            var builder = new ApplicationBuilder(new DummyServiceProvider());
+            builder.UseMiddleware(typeof(MiddlewareNoInvokeStub));
+            var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
+
+            Assert.Equal(
+                Resources.FormatException_UseMiddlewareNoInvokeMethod(
+                    UseMiddlewareExtensions.InvokeMethodName,
+                    UseMiddlewareExtensions.InvokeAsyncMethodName, typeof(MiddlewareNoInvokeStub)),
+                exception.Message);
+        }
+
+        [Fact]
+        public void UseMiddleware_MutlipleInvokeMethods_ThrowsException()
+        {
+            var builder = new ApplicationBuilder(new DummyServiceProvider());
+            builder.UseMiddleware(typeof(MiddlewareMultipleInvokesStub));
+            var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
+
+            Assert.Equal(
+                Resources.FormatException_UseMiddleMutlipleInvokes(
+                    UseMiddlewareExtensions.InvokeMethodName,
+                    UseMiddlewareExtensions.InvokeAsyncMethodName),
+                exception.Message);
+        }
+
+        [Fact]
+        public void UseMiddleware_MutlipleInvokeAsyncMethods_ThrowsException()
+        {
+            var builder = new ApplicationBuilder(new DummyServiceProvider());
+            builder.UseMiddleware(typeof(MiddlewareMultipleInvokeAsyncStub));
+            var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
+
+            Assert.Equal(
+                Resources.FormatException_UseMiddleMutlipleInvokes(
+                    UseMiddlewareExtensions.InvokeMethodName,
+                    UseMiddlewareExtensions.InvokeAsyncMethodName),
+                exception.Message);
+        }
+
+        [Fact]
+        public void UseMiddleware_MutlipleInvokeAndInvokeAsyncMethods_ThrowsException()
+        {
+            var builder = new ApplicationBuilder(new DummyServiceProvider());
+            builder.UseMiddleware(typeof(MiddlewareMultipleInvokeAndInvokeAsyncStub));
+            var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
+
+            Assert.Equal(
+                Resources.FormatException_UseMiddleMutlipleInvokes(
+                    UseMiddlewareExtensions.InvokeMethodName,
+                    UseMiddlewareExtensions.InvokeAsyncMethodName),
+                exception.Message);
+        }
+
+        [Fact]
+        public async Task UseMiddleware_ThrowsIfArgCantBeResolvedFromContainer()
+        {
+            var builder = new ApplicationBuilder(new DummyServiceProvider());
+            builder.UseMiddleware(typeof(MiddlewareInjectInvokeNoService));
+            var app = builder.Build();
+            var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => app(new DefaultHttpContext()));
+            Assert.Equal(
+                Resources.FormatException_InvokeMiddlewareNoService(
+                    typeof(object),
+                    typeof(MiddlewareInjectInvokeNoService)),
+                exception.Message);
+        }
+
+        [Fact]
+        public void UseMiddlewareWithInvokeArg()
+        {
+            var builder = new ApplicationBuilder(new DummyServiceProvider());
+            builder.UseMiddleware(typeof(MiddlewareInjectInvoke));
+            var app = builder.Build();
+            app(new DefaultHttpContext());
+        }
+
+        [Fact]
+        public void UseMiddlewareWithIvokeWithOutAndRefThrows()
+        {
+            var mockServiceProvider = new DummyServiceProvider();
+            var builder = new ApplicationBuilder(mockServiceProvider);
+            builder.UseMiddleware(typeof(MiddlewareInjectWithOutAndRefParams));
+            var exception = Assert.Throws<NotSupportedException>(() => builder.Build());
+        }
+
+        [Fact]
+        public void UseMiddlewareWithIMiddlewareThrowsIfParametersSpecified()
+        {
+            var mockServiceProvider = new DummyServiceProvider();
+            var builder = new ApplicationBuilder(mockServiceProvider);
+            var exception = Assert.Throws<NotSupportedException>(() => builder.UseMiddleware(typeof(Middleware), "arg"));
+            Assert.Equal(Resources.FormatException_UseMiddlewareExplicitArgumentsNotSupported(typeof(IMiddleware)), exception.Message);
+        }
+
+        [Fact]
+        public async Task UseMiddlewareWithIMiddlewareThrowsIfNoIMiddlewareFactoryRegistered()
+        {
+            var mockServiceProvider = new DummyServiceProvider();
+            var builder = new ApplicationBuilder(mockServiceProvider);
+            builder.UseMiddleware(typeof(Middleware));
+            var app = builder.Build();
+            var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
+            {
+                var context = new DefaultHttpContext();
+                var sp = new DummyServiceProvider();
+                context.RequestServices = sp;
+                await app(context);
+            });
+            Assert.Equal(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)), exception.Message);
+        }
+
+        [Fact]
+        public async Task UseMiddlewareWithIMiddlewareThrowsIfMiddlewareFactoryCreateReturnsNull()
+        {
+            var mockServiceProvider = new DummyServiceProvider();
+            var builder = new ApplicationBuilder(mockServiceProvider);
+            builder.UseMiddleware(typeof(Middleware));
+            var app = builder.Build();
+            var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
+            {
+                var context = new DefaultHttpContext();
+                var sp = new DummyServiceProvider();
+                sp.AddService(typeof(IMiddlewareFactory), new BadMiddlewareFactory());
+                context.RequestServices = sp;
+                await app(context);
+            });
+
+            Assert.Equal(
+                Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(
+                    typeof(BadMiddlewareFactory),
+                    typeof(Middleware)),
+                exception.Message);
+        }
+
+        [Fact]
+        public async Task UseMiddlewareWithIMiddlewareWorks()
+        {
+            var mockServiceProvider = new DummyServiceProvider();
+            var builder = new ApplicationBuilder(mockServiceProvider);
+            builder.UseMiddleware(typeof(Middleware));
+            var app = builder.Build();
+            var context = new DefaultHttpContext();
+            var sp = new DummyServiceProvider();
+            var middlewareFactory = new BasicMiddlewareFactory();
+            sp.AddService(typeof(IMiddlewareFactory), middlewareFactory);
+            context.RequestServices = sp;
+            await app(context);
+            Assert.True(Assert.IsType<bool>(context.Items["before"]));
+            Assert.True(Assert.IsType<bool>(context.Items["after"]));
+            Assert.NotNull(middlewareFactory.Created);
+            Assert.NotNull(middlewareFactory.Released);
+            Assert.IsType<Middleware>(middlewareFactory.Created);
+            Assert.IsType<Middleware>(middlewareFactory.Released);
+            Assert.Same(middlewareFactory.Created, middlewareFactory.Released);
+        }
+
+        public class Middleware : IMiddleware
+        {
+            public async Task InvokeAsync(HttpContext context, RequestDelegate next)
+            {
+                context.Items["before"] = true;
+                await next(context);
+                context.Items["after"] = true;
+            }
+        }
+
+        public class BasicMiddlewareFactory : IMiddlewareFactory
+        {
+            public IMiddleware Created { get; private set; }
+            public IMiddleware Released { get; private set; }
+
+            public IMiddleware Create(Type middlewareType)
+            {
+                Created = Activator.CreateInstance(middlewareType) as IMiddleware;
+                return Created;
+            }
+
+            public void Release(IMiddleware middleware)
+            {
+                Released = middleware;
+            }
+        }
+
+        public class BadMiddlewareFactory : IMiddlewareFactory
+        {
+            public IMiddleware Create(Type middlewareType) => null;
+
+            public void Release(IMiddleware middleware) { }
+        }
+
+        private class DummyServiceProvider : IServiceProvider
+        {
+            private Dictionary<Type, object> _services = new Dictionary<Type, object>();
+
+            public void AddService(Type type, object value) => _services[type] = value;
+
+            public object GetService(Type serviceType)
+            {
+                if (serviceType == typeof(IServiceProvider))
+                {
+                    return this;
+                }
+
+                if (_services.TryGetValue(serviceType, out object value))
+                {
+                    return value;
+                }
+                return null;
+            }
+        }
+
+        public class MiddlewareInjectWithOutAndRefParams
+        {
+            public MiddlewareInjectWithOutAndRefParams(RequestDelegate next) { }
+
+            public Task Invoke(HttpContext context, ref IServiceProvider sp1, out IServiceProvider sp2)
+            {
+                sp1 = null;
+                sp2 = null;
+                return Task.FromResult(0);
+            }
+        }
+
+        private class MiddlewareInjectInvokeNoService
+        {
+            public MiddlewareInjectInvokeNoService(RequestDelegate next) { }
+
+            public Task Invoke(HttpContext context, object value) => Task.CompletedTask;
+        }
+
+        private class MiddlewareInjectInvoke
+        {
+            public MiddlewareInjectInvoke(RequestDelegate next) { }
+
+            public Task Invoke(HttpContext context, IServiceProvider provider) => Task.CompletedTask;
+        }
+
+        private class MiddlewareNoParametersStub
+        {
+            public MiddlewareNoParametersStub(RequestDelegate next) { }
+
+            public Task Invoke() => Task.CompletedTask;
+        }
+
+        private class MiddlewareAsyncNoParametersStub
+        {
+            public MiddlewareAsyncNoParametersStub(RequestDelegate next) { }
+
+            public Task InvokeAsync() => Task.CompletedTask;
+        }
+
+        private class MiddlewareNonTaskReturnStub
+        {
+            public MiddlewareNonTaskReturnStub(RequestDelegate next) { }
+
+            public int Invoke() => 0;
+        }
+
+        private class MiddlewareAsyncNonTaskReturnStub
+        {
+            public MiddlewareAsyncNonTaskReturnStub(RequestDelegate next) { }
+
+            public int InvokeAsync() => 0;
+        }
+
+        private class MiddlewareNoInvokeStub
+        {
+            public MiddlewareNoInvokeStub(RequestDelegate next) { }
+        }
+
+        private class MiddlewareMultipleInvokesStub
+        {
+            public MiddlewareMultipleInvokesStub(RequestDelegate next) { }
+
+            public Task Invoke(HttpContext context) => Task.CompletedTask;
+
+            public Task Invoke(HttpContext context, int i) => Task.CompletedTask;
+        }
+
+        private class MiddlewareMultipleInvokeAsyncStub
+        {
+            public MiddlewareMultipleInvokeAsyncStub(RequestDelegate next) { }
+
+            public Task InvokeAsync(HttpContext context) => Task.CompletedTask;
+
+            public Task InvokeAsync(HttpContext context, int i) => Task.CompletedTask;
+        }
+
+        private class MiddlewareMultipleInvokeAndInvokeAsyncStub
+        {
+            public MiddlewareMultipleInvokeAndInvokeAsyncStub(RequestDelegate next) { }
+
+            public Task Invoke(HttpContext context) => Task.CompletedTask;
+
+            public Task InvokeAsync(HttpContext context) => Task.CompletedTask;
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/test/UsePathBaseExtensionsTests.cs b/src/Http/Http.Abstractions/test/UsePathBaseExtensionsTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4b8e9d71cb5e23fe8a5f564ab45d48a36504cb2f
--- /dev/null
+++ b/src/Http/Http.Abstractions/test/UsePathBaseExtensionsTests.cs
@@ -0,0 +1,168 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder.Internal;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Builder.Extensions
+{
+    public class UsePathBaseExtensionsTests
+    {
+        [Theory]
+        [InlineData(null)]
+        [InlineData("")]
+        [InlineData("/")]
+        public void EmptyOrNullPathBase_DoNotAddMiddleware(string pathBase)
+        {
+            // Arrange
+            var useCalled = false;
+            var builder = new ApplicationBuilderWrapper(CreateBuilder(), () => useCalled = true)
+                .UsePathBase(pathBase);
+
+            // Act
+            builder.Build();
+
+            // Assert
+            Assert.False(useCalled);
+        }
+
+        private class ApplicationBuilderWrapper : IApplicationBuilder
+        {
+            private readonly IApplicationBuilder _wrappedBuilder;
+            private readonly Action _useCallback;
+
+            public ApplicationBuilderWrapper(IApplicationBuilder applicationBuilder, Action useCallback)
+            {
+                _wrappedBuilder = applicationBuilder;
+                _useCallback = useCallback;
+            }
+
+            public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
+            {
+                _useCallback();
+                return _wrappedBuilder.Use(middleware);
+            }
+
+            public IServiceProvider ApplicationServices
+            {
+                get { return _wrappedBuilder.ApplicationServices; }
+                set { _wrappedBuilder.ApplicationServices = value; }
+            }
+
+            public IDictionary<string, object> Properties => _wrappedBuilder.Properties;
+            public IFeatureCollection ServerFeatures => _wrappedBuilder.ServerFeatures;
+            public RequestDelegate Build() => _wrappedBuilder.Build();
+            public IApplicationBuilder New() => _wrappedBuilder.New();
+
+        }
+
+        [Theory]
+        [InlineData("/base", "", "/base", "/base", "")]
+        [InlineData("/base", "", "/base/", "/base", "/")]
+        [InlineData("/base", "", "/base/something", "/base", "/something")]
+        [InlineData("/base", "", "/base/something/", "/base", "/something/")]
+        [InlineData("/base/more", "", "/base/more", "/base/more", "")]
+        [InlineData("/base/more", "", "/base/more/something", "/base/more", "/something")]
+        [InlineData("/base/more", "", "/base/more/something/", "/base/more", "/something/")]
+        [InlineData("/base", "/oldbase", "/base", "/oldbase/base", "")]
+        [InlineData("/base", "/oldbase", "/base/", "/oldbase/base", "/")]
+        [InlineData("/base", "/oldbase", "/base/something", "/oldbase/base", "/something")]
+        [InlineData("/base", "/oldbase", "/base/something/", "/oldbase/base", "/something/")]
+        [InlineData("/base/more", "/oldbase", "/base/more", "/oldbase/base/more", "")]
+        [InlineData("/base/more", "/oldbase", "/base/more/something", "/oldbase/base/more", "/something")]
+        [InlineData("/base/more", "/oldbase", "/base/more/something/", "/oldbase/base/more", "/something/")]
+        public void RequestPathBaseContainingPathBase_IsSplit(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
+        {
+            TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
+        }
+
+        [Theory]
+        [InlineData("/base", "", "/something", "", "/something")]
+        [InlineData("/base", "", "/baseandsomething", "", "/baseandsomething")]
+        [InlineData("/base", "", "/ba", "", "/ba")]
+        [InlineData("/base", "", "/ba/se", "", "/ba/se")]
+        [InlineData("/base", "/oldbase", "/something", "/oldbase", "/something")]
+        [InlineData("/base", "/oldbase", "/baseandsomething", "/oldbase", "/baseandsomething")]
+        [InlineData("/base", "/oldbase", "/ba", "/oldbase", "/ba")]
+        [InlineData("/base", "/oldbase", "/ba/se", "/oldbase", "/ba/se")]
+        public void RequestPathBaseNotContainingPathBase_IsNotSplit(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
+        {
+            TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
+        }
+
+        [Theory]
+        [InlineData("", "", "/", "", "/")]
+        [InlineData("/", "", "/", "", "/")]
+        [InlineData("/base", "", "/base/", "/base", "/")]
+        [InlineData("/base/", "", "/base", "/base", "")]
+        [InlineData("/base/", "", "/base/", "/base", "/")]
+        [InlineData("", "/oldbase", "/", "/oldbase", "/")]
+        [InlineData("/", "/oldbase", "/", "/oldbase", "/")]
+        [InlineData("/base", "/oldbase", "/base/", "/oldbase/base", "/")]
+        [InlineData("/base/", "/oldbase", "/base", "/oldbase/base", "")]
+        [InlineData("/base/", "/oldbase", "/base/", "/oldbase/base", "/")]
+        public void PathBaseNeverEndsWithSlash(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
+        {
+            TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
+        }
+
+        [Theory]
+        [InlineData("/base", "", "/Base/Something", "/Base", "/Something")]
+        [InlineData("/base", "/OldBase", "/Base/Something", "/OldBase/Base", "/Something")]
+        public void PathBaseAndPathPreserveRequestCasing(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
+        {
+            TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
+        }
+
+        [Theory]
+        [InlineData("/b♫se", "", "/b♫se/something", "/b♫se", "/something")]
+        [InlineData("/b♫se", "", "/B♫se/something", "/B♫se", "/something")]
+        [InlineData("/b♫se", "", "/b♫se/Something", "/b♫se", "/Something")]
+        [InlineData("/b♫se", "/oldb♫se", "/b♫se/something", "/oldb♫se/b♫se", "/something")]
+        [InlineData("/b♫se", "/oldb♫se", "/b♫se/Something", "/oldb♫se/b♫se", "/Something")]
+        [InlineData("/b♫se", "/oldb♫se", "/B♫se/something", "/oldb♫se/B♫se", "/something")]
+        public void PathBaseCanHaveUnicodeCharacters(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
+        {
+            TestPathBase(registeredPathBase, pathBase, requestPath, expectedPathBase, expectedPath);
+        }
+
+        private static void TestPathBase(string registeredPathBase, string pathBase, string requestPath, string expectedPathBase, string expectedPath)
+        {
+            HttpContext requestContext = CreateRequest(pathBase, requestPath);
+            var builder = CreateBuilder()
+                .UsePathBase(registeredPathBase);
+            builder.Run(context =>
+            {
+                context.Items["test.Path"] = context.Request.Path;
+                context.Items["test.PathBase"] = context.Request.PathBase;
+                return Task.FromResult(0);
+            });
+            builder.Build().Invoke(requestContext).Wait();
+
+            // Assert path and pathBase are split after middleware
+            Assert.Equal(expectedPath, ((PathString)requestContext.Items["test.Path"]).Value);
+            Assert.Equal(expectedPathBase, ((PathString)requestContext.Items["test.PathBase"]).Value);
+            // Assert path and pathBase are reset after request
+            Assert.Equal(pathBase, requestContext.Request.PathBase.Value);
+            Assert.Equal(requestPath, requestContext.Request.Path.Value);
+        }
+
+        private static HttpContext CreateRequest(string pathBase, string requestPath)
+        {
+            HttpContext context = new DefaultHttpContext();
+            context.Request.PathBase = new PathString(pathBase);
+            context.Request.Path = new PathString(requestPath);
+            return context;
+        }
+
+        private static ApplicationBuilder CreateBuilder()
+        {
+            return new ApplicationBuilder(serviceProvider: null);
+        }
+    }
+}
diff --git a/src/Http/Http.Abstractions/test/UseWhenExtensionsTests.cs b/src/Http/Http.Abstractions/test/UseWhenExtensionsTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..902a003b26dac1036f9270ce51dcdca6b62619ec
--- /dev/null
+++ b/src/Http/Http.Abstractions/test/UseWhenExtensionsTests.cs
@@ -0,0 +1,170 @@
+// 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.Builder.Internal;
+using Microsoft.AspNetCore.Http;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Builder.Extensions
+{
+    public class UseWhenExtensionsTests
+    {
+        [Fact]
+        public void NullArguments_ArgumentNullException()
+        {
+            // Arrange
+            var builder = CreateBuilder();
+
+            // Act
+            Action nullPredicate = () => builder.UseWhen(null, app => { });
+            Action nullConfiguration = () => builder.UseWhen(TruePredicate, null);
+
+            // Assert
+            Assert.Throws<ArgumentNullException>(nullPredicate);
+            Assert.Throws<ArgumentNullException>(nullConfiguration);
+        }
+
+        [Fact]
+        public void PredicateTrue_BranchTaken_WillRejoin()
+        {
+            // Arrange
+            var context = CreateContext();
+            var parent = CreateBuilder();
+
+            parent.UseWhen(TruePredicate, child =>
+            {
+                child.UseWhen(TruePredicate, grandchild =>
+                {
+                    grandchild.Use(Increment("grandchild"));
+                });
+
+                child.Use(Increment("child"));
+            });
+
+            parent.Use(Increment("parent"));
+
+            // Act
+            parent.Build().Invoke(context).Wait();
+
+            // Assert
+            Assert.Equal(1, Count(context, "parent"));
+            Assert.Equal(1, Count(context, "child"));
+            Assert.Equal(1, Count(context, "grandchild"));
+        }
+
+        [Fact]
+        public void PredicateTrue_BranchTaken_CanTerminate()
+        {
+            // Arrange
+            var context = CreateContext();
+            var parent = CreateBuilder();
+
+            parent.UseWhen(TruePredicate, child =>
+            {
+                child.UseWhen(TruePredicate, grandchild =>
+                {
+                    grandchild.Use(Increment("grandchild", terminate: true));
+                });
+
+                child.Use(Increment("child"));
+            });
+
+            parent.Use(Increment("parent"));
+
+            // Act
+            parent.Build().Invoke(context).Wait();
+
+            // Assert
+            Assert.Equal(0, Count(context, "parent"));
+            Assert.Equal(0, Count(context, "child"));
+            Assert.Equal(1, Count(context, "grandchild"));
+        }
+
+        [Fact]
+        public void PredicateFalse_PassThrough()
+        {
+            // Arrange
+            var context = CreateContext();
+            var parent = CreateBuilder();
+
+            parent.UseWhen(FalsePredicate, child =>
+            {
+                child.Use(Increment("child"));
+            });
+
+            parent.Use(Increment("parent"));
+
+            // Act
+            parent.Build().Invoke(context).Wait();
+
+            // Assert
+            Assert.Equal(1, Count(context, "parent"));
+            Assert.Equal(0, Count(context, "child"));
+        }
+
+        private static HttpContext CreateContext()
+        {
+            return new DefaultHttpContext();
+        }
+
+        private static ApplicationBuilder CreateBuilder()
+        {
+            return new ApplicationBuilder(serviceProvider: null);
+        }
+
+        private static bool TruePredicate(HttpContext context)
+        {
+            return true;
+        }
+
+        private static bool FalsePredicate(HttpContext context)
+        {
+            return false;
+        }
+
+        private static Func<HttpContext, Func<Task>, Task> Increment(string key, bool terminate = false)
+        {
+            return (context, next) =>
+            {
+                if (!context.Items.ContainsKey(key))
+                {
+                    context.Items[key] = 1;
+                }
+                else
+                {
+                    var item = context.Items[key];
+
+                    if (item is int)
+                    {
+                        context.Items[key] = 1 + (int)item;
+                    }
+                    else
+                    {
+                        context.Items[key] = 1;
+                    }
+                }
+
+                return terminate ? Task.FromResult<object>(null) : next();
+            };
+        }
+
+        private static int Count(HttpContext context, string key)
+        {
+            if (!context.Items.ContainsKey(key))
+            {
+                return 0;
+            }
+
+            var item = context.Items[key];
+
+            if (item is int)
+            {
+                return (int)item;
+            }
+
+            return 0;
+        }
+    }
+}
diff --git a/src/Http/Http.Extensions/src/HeaderDictionaryTypeExtensions.cs b/src/Http/Http.Extensions/src/HeaderDictionaryTypeExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1723ee6fd5c032cda4b197484f1daf7c57862f28
--- /dev/null
+++ b/src/Http/Http.Extensions/src/HeaderDictionaryTypeExtensions.cs
@@ -0,0 +1,287 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using Microsoft.AspNetCore.Http.Headers;
+using Microsoft.Extensions.Primitives;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public static class HeaderDictionaryTypeExtensions
+    {
+        public static RequestHeaders GetTypedHeaders(this HttpRequest request)
+        {
+            return new RequestHeaders(request.Headers);
+        }
+
+        public static ResponseHeaders GetTypedHeaders(this HttpResponse response)
+        {
+            return new ResponseHeaders(response.Headers);
+        }
+
+        // These are all shared helpers used by both RequestHeaders and ResponseHeaders
+
+        internal static DateTimeOffset? GetDate(this IHeaderDictionary headers, string name)
+        {
+            if (headers == null)
+            {
+                throw new ArgumentNullException(nameof(headers));
+            }
+
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            return headers.Get<DateTimeOffset?>(name);
+        }
+
+        internal static void Set(this IHeaderDictionary headers, string name, object value)
+        {
+            if (headers == null)
+            {
+                throw new ArgumentNullException(nameof(headers));
+            }
+
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            if (value == null)
+            {
+                headers.Remove(name);
+            }
+            else
+            {
+                headers[name] = value.ToString();
+            }
+        }
+
+        internal static void SetList<T>(this IHeaderDictionary headers, string name, IList<T> values)
+        {
+            if (headers == null)
+            {
+                throw new ArgumentNullException(nameof(headers));
+            }
+
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            if (values == null || values.Count == 0)
+            {
+                headers.Remove(name);
+            }
+            else if (values.Count == 1)
+            {
+                headers[name] = new StringValues(values[0].ToString());
+            }
+            else
+            {
+                var newValues = new string[values.Count];
+                for (var i = 0; i < values.Count; i++)
+                {
+                    newValues[i] = values[i].ToString();
+                }
+                headers[name] = new StringValues(newValues);
+            }
+        }
+
+        public static void AppendList<T>(this IHeaderDictionary Headers, string name, IList<T> values)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            if (values == null)
+            {
+                throw new ArgumentNullException(nameof(values));
+            }
+
+            switch (values.Count)
+            {
+                case 0:
+                    Headers.Append(name, StringValues.Empty);
+                    break;
+                case 1:
+                    Headers.Append(name, new StringValues(values[0].ToString()));
+                    break;
+                default:
+                    var newValues = new string[values.Count];
+                    for (var i = 0; i < values.Count; i++)
+                    {
+                        newValues[i] = values[i].ToString();
+                    }
+                    Headers.Append(name, new StringValues(newValues));
+                    break;
+            }
+        }
+
+        internal static void SetDate(this IHeaderDictionary headers, string name, DateTimeOffset? value)
+        {
+            if (headers == null)
+            {
+                throw new ArgumentNullException(nameof(headers));
+            }
+
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            if (value.HasValue)
+            {
+                headers[name] = HeaderUtilities.FormatDate(value.Value);
+            }
+            else
+            {
+                headers.Remove(name);
+            }
+        }
+
+        private static IDictionary<Type, object> KnownParsers = new Dictionary<Type, object>()
+        {
+            { typeof(CacheControlHeaderValue), new Func<string, CacheControlHeaderValue>(value => { CacheControlHeaderValue result; return CacheControlHeaderValue.TryParse(value, out result) ? result : null; }) },
+            { typeof(ContentDispositionHeaderValue), new Func<string, ContentDispositionHeaderValue>(value => { ContentDispositionHeaderValue result; return ContentDispositionHeaderValue.TryParse(value, out result) ? result : null; }) },
+            { typeof(ContentRangeHeaderValue), new Func<string, ContentRangeHeaderValue>(value => { ContentRangeHeaderValue result; return ContentRangeHeaderValue.TryParse(value, out result) ? result : null; }) },
+            { typeof(MediaTypeHeaderValue), new Func<string, MediaTypeHeaderValue>(value => { MediaTypeHeaderValue result; return MediaTypeHeaderValue.TryParse(value, out result) ? result : null; }) },
+            { typeof(RangeConditionHeaderValue), new Func<string, RangeConditionHeaderValue>(value => { RangeConditionHeaderValue result; return RangeConditionHeaderValue.TryParse(value, out result) ? result : null; }) },
+            { typeof(RangeHeaderValue), new Func<string, RangeHeaderValue>(value => { RangeHeaderValue result; return RangeHeaderValue.TryParse(value, out result) ? result : null; }) },
+            { typeof(EntityTagHeaderValue), new Func<string, EntityTagHeaderValue>(value => { EntityTagHeaderValue result; return EntityTagHeaderValue.TryParse(value, out result) ? result : null; }) },
+            { typeof(DateTimeOffset?), new Func<string, DateTimeOffset?>(value => { DateTimeOffset result; return HeaderUtilities.TryParseDate(value, out result) ? result : (DateTimeOffset?)null; }) },
+            { typeof(long?), new Func<string, long?>(value => { long result; return HeaderUtilities.TryParseNonNegativeInt64(value, out result) ? result : (long?)null; }) },
+        };
+
+        private static IDictionary<Type, object> KnownListParsers = new Dictionary<Type, object>()
+        {
+            { typeof(MediaTypeHeaderValue), new Func<IList<string>, IList<MediaTypeHeaderValue>>(value => { IList<MediaTypeHeaderValue> result; return MediaTypeHeaderValue.TryParseList(value, out result) ? result : null; })  },
+            { typeof(StringWithQualityHeaderValue), new Func<IList<string>, IList<StringWithQualityHeaderValue>>(value => { IList<StringWithQualityHeaderValue> result; return StringWithQualityHeaderValue.TryParseList(value, out result) ? result : null; })  },
+            { typeof(CookieHeaderValue), new Func<IList<string>, IList<CookieHeaderValue>>(value => { IList<CookieHeaderValue> result; return CookieHeaderValue.TryParseList(value, out result) ? result : null; })  },
+            { typeof(EntityTagHeaderValue), new Func<IList<string>, IList<EntityTagHeaderValue>>(value => { IList<EntityTagHeaderValue> result; return EntityTagHeaderValue.TryParseList(value, out result) ? result : null; })  },
+            { typeof(SetCookieHeaderValue), new Func<IList<string>, IList<SetCookieHeaderValue>>(value => { IList<SetCookieHeaderValue> result; return SetCookieHeaderValue.TryParseList(value, out result) ? result : null; })  },
+        };
+
+        internal static T Get<T>(this IHeaderDictionary headers, string name)
+        {
+            if (headers == null)
+            {
+                throw new ArgumentNullException(nameof(headers));
+            }
+
+            object temp;
+            var value = headers[name];
+
+            if (StringValues.IsNullOrEmpty(value))
+            {
+                return default(T);
+            }
+
+            if (KnownParsers.TryGetValue(typeof(T), out temp))
+            {
+                var func = (Func<string, T>)temp;
+                return func(value);
+            }
+
+            return GetViaReflection<T>(value.ToString());
+        }
+
+        internal static IList<T> GetList<T>(this IHeaderDictionary headers, string name)
+        {
+            if (headers == null)
+            {
+                throw new ArgumentNullException(nameof(headers));
+            }
+
+            object temp;
+            var values = headers[name];
+
+            if (StringValues.IsNullOrEmpty(values))
+            {
+                return null;
+            }
+
+            if (KnownListParsers.TryGetValue(typeof(T), out temp))
+            {
+                var func = (Func<IList<string>, IList<T>>)temp;
+                return func(values);
+            }
+
+            return GetListViaReflection<T>(values);
+        }
+
+        private static T GetViaReflection<T>(string value)
+        {
+            // TODO: Cache the reflected type for later? Only if success?
+            var type = typeof(T);
+            var method = type.GetMethods(BindingFlags.Public | BindingFlags.Static)
+                .FirstOrDefault(methodInfo =>
+                {
+                    if (string.Equals("TryParse", methodInfo.Name, StringComparison.Ordinal)
+                        && methodInfo.ReturnParameter.ParameterType.Equals(typeof(bool)))
+                    {
+                        var methodParams = methodInfo.GetParameters();
+                        return methodParams.Length == 2
+                            && methodParams[0].ParameterType.Equals(typeof(string))
+                            && methodParams[1].IsOut
+                            && methodParams[1].ParameterType.Equals(type.MakeByRefType());
+                    }
+                    return false;
+                });
+
+            if (method == null)
+            {
+                throw new NotSupportedException(string.Format(
+                    "The given type '{0}' does not have a TryParse method with the required signature 'public static bool TryParse(string, out {0}).", nameof(T)));
+            }
+
+            var parameters = new object[] { value, null };
+            var success = (bool)method.Invoke(null, parameters);
+            if (success)
+            {
+                return (T)parameters[1];
+            }
+            return default(T);
+        }
+
+        private static IList<T> GetListViaReflection<T>(StringValues values)
+        {
+            // TODO: Cache the reflected type for later? Only if success?
+            var type = typeof(T);
+            var method = type.GetMethods(BindingFlags.Public | BindingFlags.Static)
+                .FirstOrDefault(methodInfo =>
+                {
+                    if (string.Equals("TryParseList", methodInfo.Name, StringComparison.Ordinal)
+                        && methodInfo.ReturnParameter.ParameterType.Equals(typeof(Boolean)))
+                    {
+                        var methodParams = methodInfo.GetParameters();
+                        return methodParams.Length == 2
+                            && methodParams[0].ParameterType.Equals(typeof(IList<string>))
+                            && methodParams[1].IsOut
+                            && methodParams[1].ParameterType.Equals(typeof(IList<T>).MakeByRefType());
+                    }
+                    return false;
+                });
+
+            if (method == null)
+            {
+                throw new NotSupportedException(string.Format(
+                    "The given type '{0}' does not have a TryParseList method with the required signature 'public static bool TryParseList(IList<string>, out IList<{0}>).", nameof(T)));
+            }
+
+            var parameters = new object[] { values, null };
+            var success = (bool)method.Invoke(null, parameters);
+            if (success)
+            {
+                return (IList<T>)parameters[1];
+            }
+            return null;
+        }
+    }
+}
diff --git a/src/Http/Http.Extensions/src/HttpRequestMultipartExtensions.cs b/src/Http/Http.Extensions/src/HttpRequestMultipartExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..da9188dad37d7721ed40aefb2007f3f954ddd8e5
--- /dev/null
+++ b/src/Http/Http.Extensions/src/HttpRequestMultipartExtensions.cs
@@ -0,0 +1,26 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http.Extensions
+{
+    public static class HttpRequestMultipartExtensions
+    {
+        public static string GetMultipartBoundary(this HttpRequest request)
+        {
+            if (request == null)
+            {
+                throw new ArgumentNullException(nameof(request));
+            }
+
+            MediaTypeHeaderValue mediaType;
+            if (!MediaTypeHeaderValue.TryParse(request.ContentType, out mediaType))
+            {
+                return string.Empty;
+            }
+            return HeaderUtilities.RemoveQuotes(mediaType.Boundary).ToString();
+        }
+    }
+}
diff --git a/src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj b/src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..25ae2af17a7757357ba29555dba51984b7ab2482
--- /dev/null
+++ b/src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj
@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>ASP.NET Core common extension methods for HTTP abstractions, HTTP headers, HTTP request/response, and session state.</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <NoWarn>$(NoWarn);CS1591</NoWarn>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore</PackageTags>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Http.Abstractions" />
+    <Reference Include="Microsoft.Net.Http.Headers" />
+    <Reference Include="Microsoft.Extensions.FileProviders.Abstractions" />
+    <Reference Include="System.Buffers" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/Http.Extensions/src/QueryBuilder.cs b/src/Http/Http.Extensions/src/QueryBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e9feb391b17b7dc664a4478d43413c3a5f049ccc
--- /dev/null
+++ b/src/Http/Http.Extensions/src/QueryBuilder.cs
@@ -0,0 +1,81 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.Encodings.Web;
+
+namespace Microsoft.AspNetCore.Http.Extensions
+{
+    // The IEnumerable interface is required for the collection initialization syntax: new QueryBuilder() { { "key", "value" } };
+    public class QueryBuilder : IEnumerable<KeyValuePair<string, string>>
+    {
+        private IList<KeyValuePair<string, string>> _params;
+
+        public QueryBuilder()
+        {
+            _params = new List<KeyValuePair<string, string>>();
+        }
+
+        public QueryBuilder(IEnumerable<KeyValuePair<string, string>> parameters)
+        {
+            _params = new List<KeyValuePair<string, string>>(parameters);
+        }
+
+        public void Add(string key, IEnumerable<string> values)
+        {
+            foreach (var value in values)
+            {
+                _params.Add(new KeyValuePair<string, string>(key, value));
+            }
+        }
+
+        public void Add(string key, string value)
+        {
+            _params.Add(new KeyValuePair<string, string>(key, value));
+        }
+
+        public override string ToString()
+        {
+            var builder = new StringBuilder();
+            bool first = true;
+            for (int i = 0; i < _params.Count; i++)
+            {
+                var pair = _params[i];
+                builder.Append(first ? "?" : "&");
+                first = false;
+                builder.Append(UrlEncoder.Default.Encode(pair.Key));
+                builder.Append("=");
+                builder.Append(UrlEncoder.Default.Encode(pair.Value));
+            }
+
+            return builder.ToString();
+        }
+
+        public QueryString ToQueryString()
+        {
+            return new QueryString(ToString());
+        }
+
+        public override int GetHashCode()
+        {
+            return ToQueryString().GetHashCode();
+        }
+
+        public override bool Equals(object obj)
+        {
+            return ToQueryString().Equals(obj);
+        }
+
+        public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
+        {
+            return _params.GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return _params.GetEnumerator();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Extensions/src/RequestHeaders.cs b/src/Http/Http.Extensions/src/RequestHeaders.cs
new file mode 100644
index 0000000000000000000000000000000000000000..12246922d43aa7eb73e9094c809ac93229dd64cf
--- /dev/null
+++ b/src/Http/Http.Extensions/src/RequestHeaders.cs
@@ -0,0 +1,332 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Http.Extensions;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http.Headers
+{
+    public class RequestHeaders
+    {
+        public RequestHeaders(IHeaderDictionary headers)
+        {
+            if (headers == null)
+            {
+                throw new ArgumentNullException(nameof(headers));
+            }
+
+            Headers = headers;
+        }
+
+        public IHeaderDictionary Headers { get; }
+
+        public IList<MediaTypeHeaderValue> Accept
+        {
+            get
+            {
+                return Headers.GetList<MediaTypeHeaderValue>(HeaderNames.Accept);
+            }
+            set
+            {
+                Headers.SetList(HeaderNames.Accept, value);
+            }
+        }
+
+        public IList<StringWithQualityHeaderValue> AcceptCharset
+        {
+            get
+            {
+                return Headers.GetList<StringWithQualityHeaderValue>(HeaderNames.AcceptCharset);
+            }
+            set
+            {
+                Headers.SetList(HeaderNames.AcceptCharset, value);
+            }
+        }
+
+        public IList<StringWithQualityHeaderValue> AcceptEncoding
+        {
+            get
+            {
+                return Headers.GetList<StringWithQualityHeaderValue>(HeaderNames.AcceptEncoding);
+            }
+            set
+            {
+                Headers.SetList(HeaderNames.AcceptEncoding, value);
+            }
+        }
+
+        public IList<StringWithQualityHeaderValue> AcceptLanguage
+        {
+            get
+            {
+                return Headers.GetList<StringWithQualityHeaderValue>(HeaderNames.AcceptLanguage);
+            }
+            set
+            {
+                Headers.SetList(HeaderNames.AcceptLanguage, value);
+            }
+        }
+
+        public CacheControlHeaderValue CacheControl
+        {
+            get
+            {
+                return Headers.Get<CacheControlHeaderValue>(HeaderNames.CacheControl);
+            }
+            set
+            {
+                Headers.Set(HeaderNames.CacheControl, value);
+            }
+        }
+
+        public ContentDispositionHeaderValue ContentDisposition
+        {
+            get
+            {
+                return Headers.Get<ContentDispositionHeaderValue>(HeaderNames.ContentDisposition);
+            }
+            set
+            {
+                Headers.Set(HeaderNames.ContentDisposition, value);
+            }
+        }
+
+        public long? ContentLength
+        {
+            get
+            {
+                return Headers.ContentLength;
+            }
+            set
+            {
+                Headers.ContentLength = value;
+            }
+        }
+
+        public ContentRangeHeaderValue ContentRange
+        {
+            get
+            {
+                return Headers.Get<ContentRangeHeaderValue>(HeaderNames.ContentRange);
+            }
+            set
+            {
+                Headers.Set(HeaderNames.ContentRange, value);
+            }
+        }
+
+        public MediaTypeHeaderValue ContentType
+        {
+            get
+            {
+                return Headers.Get<MediaTypeHeaderValue>(HeaderNames.ContentType);
+            }
+            set
+            {
+                Headers.Set(HeaderNames.ContentType, value);
+            }
+        }
+
+        public IList<CookieHeaderValue> Cookie
+        {
+            get
+            {
+                return Headers.GetList<CookieHeaderValue>(HeaderNames.Cookie);
+            }
+            set
+            {
+                Headers.SetList(HeaderNames.Cookie, value);
+            }
+        }
+
+        public DateTimeOffset? Date
+        {
+            get
+            {
+                return Headers.GetDate(HeaderNames.Date);
+            }
+            set
+            {
+                Headers.SetDate(HeaderNames.Date, value);
+            }
+        }
+
+        public DateTimeOffset? Expires
+        {
+            get
+            {
+                return Headers.GetDate(HeaderNames.Expires);
+            }
+            set
+            {
+                Headers.SetDate(HeaderNames.Expires, value);
+            }
+        }
+
+        public HostString Host
+        {
+            get
+            {
+                return HostString.FromUriComponent(Headers[HeaderNames.Host]);
+            }
+            set
+            {
+                Headers[HeaderNames.Host] = value.ToUriComponent();
+            }
+        }
+
+        public IList<EntityTagHeaderValue> IfMatch
+        {
+            get
+            {
+                return Headers.GetList<EntityTagHeaderValue>(HeaderNames.IfMatch);
+            }
+            set
+            {
+                Headers.SetList(HeaderNames.IfMatch, value);
+            }
+        }
+
+        public DateTimeOffset? IfModifiedSince
+        {
+            get
+            {
+                return Headers.GetDate(HeaderNames.IfModifiedSince);
+            }
+            set
+            {
+                Headers.SetDate(HeaderNames.IfModifiedSince, value);
+            }
+        }
+
+        public IList<EntityTagHeaderValue> IfNoneMatch
+        {
+            get
+            {
+                return Headers.GetList<EntityTagHeaderValue>(HeaderNames.IfNoneMatch);
+            }
+            set
+            {
+                Headers.SetList(HeaderNames.IfNoneMatch, value);
+            }
+        }
+
+        public RangeConditionHeaderValue IfRange
+        {
+            get
+            {
+                return Headers.Get<RangeConditionHeaderValue>(HeaderNames.IfRange);
+            }
+            set
+            {
+                Headers.Set(HeaderNames.IfRange, value);
+            }
+        }
+
+        public DateTimeOffset? IfUnmodifiedSince
+        {
+            get
+            {
+                return Headers.GetDate(HeaderNames.IfUnmodifiedSince);
+            }
+            set
+            {
+                Headers.SetDate(HeaderNames.IfUnmodifiedSince, value);
+            }
+        }
+
+        public DateTimeOffset? LastModified
+        {
+            get
+            {
+                return Headers.GetDate(HeaderNames.LastModified);
+            }
+            set
+            {
+                Headers.SetDate(HeaderNames.LastModified, value);
+            }
+        }
+
+        public RangeHeaderValue Range
+        {
+            get
+            {
+                return Headers.Get<RangeHeaderValue>(HeaderNames.Range);
+            }
+            set
+            {
+                Headers.Set(HeaderNames.Range, value);
+            }
+        }
+
+        public Uri Referer
+        {
+            get
+            {
+                Uri uri;
+                if (Uri.TryCreate(Headers[HeaderNames.Referer], UriKind.RelativeOrAbsolute, out uri))
+                {
+                    return uri;
+                }
+                return null;
+            }
+            set
+            {
+                Headers.Set(HeaderNames.Referer, value == null ? null : UriHelper.Encode(value));
+            }
+        }
+
+        public T Get<T>(string name)
+        {
+            return Headers.Get<T>(name);
+        }
+
+        public IList<T> GetList<T>(string name)
+        {
+            return Headers.GetList<T>(name);
+        }
+
+        public void Set(string name, object value)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            Headers.Set(name, value);
+        }
+
+        public void SetList<T>(string name, IList<T> values)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            Headers.SetList<T>(name, values);
+        }
+
+        public void Append(string name, object value)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            if (value == null)
+            {
+                throw new ArgumentNullException(nameof(value));
+            }
+
+            Headers.Append(name, value.ToString());
+        }
+
+        public void AppendList<T>(string name, IList<T> values)
+        {
+            Headers.AppendList<T>(name, values);
+        }
+    }
+}
diff --git a/src/Http/Http.Extensions/src/ResponseExtensions.cs b/src/Http/Http.Extensions/src/ResponseExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6c5d92a7af41c748d84f57ecdbcc556b256a1af2
--- /dev/null
+++ b/src/Http/Http.Extensions/src/ResponseExtensions.cs
@@ -0,0 +1,26 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public static class ResponseExtensions
+    {
+        public static void Clear(this HttpResponse response)
+        {
+            if (response.HasStarted)
+            {
+                throw new InvalidOperationException("The response cannot be cleared, it has already started sending.");
+            }
+            response.StatusCode = 200;
+            response.HttpContext.Features.Get<IHttpResponseFeature>().ReasonPhrase = null;
+            response.Headers.Clear();
+            if (response.Body.CanSeek)
+            {
+                response.Body.SetLength(0);
+            }
+        }
+    }
+}
diff --git a/src/Http/Http.Extensions/src/ResponseHeaders.cs b/src/Http/Http.Extensions/src/ResponseHeaders.cs
new file mode 100644
index 0000000000000000000000000000000000000000..87e3c0318c9564c8ac1b5a0b406af15e79dff2a9
--- /dev/null
+++ b/src/Http/Http.Extensions/src/ResponseHeaders.cs
@@ -0,0 +1,211 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Http.Extensions;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http.Headers
+{
+    public class ResponseHeaders
+    {
+        public ResponseHeaders(IHeaderDictionary headers)
+        {
+            if (headers == null)
+            {
+                throw new ArgumentNullException(nameof(headers));
+            }
+
+            Headers = headers;
+        }
+
+        public IHeaderDictionary Headers { get; }
+
+        public CacheControlHeaderValue CacheControl
+        {
+            get
+            {
+                return Headers.Get<CacheControlHeaderValue>(HeaderNames.CacheControl);
+            }
+            set
+            {
+                Headers.Set(HeaderNames.CacheControl, value);
+            }
+        }
+
+        public ContentDispositionHeaderValue ContentDisposition
+        {
+            get
+            {
+                return Headers.Get<ContentDispositionHeaderValue>(HeaderNames.ContentDisposition);
+            }
+            set
+            {
+                Headers.Set(HeaderNames.ContentDisposition, value);
+            }
+        }
+
+        public long? ContentLength
+        {
+            get
+            {
+                return Headers.ContentLength;
+            }
+            set
+            {
+                Headers.ContentLength = value;
+            }
+        }
+
+        public ContentRangeHeaderValue ContentRange
+        {
+            get
+            {
+                return Headers.Get<ContentRangeHeaderValue>(HeaderNames.ContentRange);
+            }
+            set
+            {
+                Headers.Set(HeaderNames.ContentRange, value);
+            }
+        }
+
+        public MediaTypeHeaderValue ContentType
+        {
+            get
+            {
+                return Headers.Get<MediaTypeHeaderValue>(HeaderNames.ContentType);
+            }
+            set
+            {
+                Headers.Set(HeaderNames.ContentType, value);
+            }
+        }
+
+        public DateTimeOffset? Date
+        {
+            get
+            {
+                return Headers.GetDate(HeaderNames.Date);
+            }
+            set
+            {
+                Headers.SetDate(HeaderNames.Date, value);
+            }
+        }
+
+        public EntityTagHeaderValue ETag
+        {
+            get
+            {
+                return Headers.Get<EntityTagHeaderValue>(HeaderNames.ETag);
+            }
+            set
+            {
+                Headers.Set(HeaderNames.ETag, value);
+            }
+        }
+        public DateTimeOffset? Expires
+        {
+            get
+            {
+                return Headers.GetDate(HeaderNames.Expires);
+            }
+            set
+            {
+                Headers.SetDate(HeaderNames.Expires, value);
+            }
+        }
+
+        public DateTimeOffset? LastModified
+        {
+            get
+            {
+                return Headers.GetDate(HeaderNames.LastModified);
+            }
+            set
+            {
+                Headers.SetDate(HeaderNames.LastModified, value);
+            }
+        }
+
+        public Uri Location
+        {
+            get
+            {
+                Uri uri;
+                if (Uri.TryCreate(Headers[HeaderNames.Location], UriKind.RelativeOrAbsolute, out uri))
+                {
+                    return uri;
+                }
+                return null;
+            }
+            set
+            {
+                Headers.Set(HeaderNames.Location, value == null ? null : UriHelper.Encode(value));
+            }
+        }
+
+        public IList<SetCookieHeaderValue> SetCookie
+        {
+            get
+            {
+                return Headers.GetList<SetCookieHeaderValue>(HeaderNames.SetCookie);
+            }
+            set
+            {
+                Headers.SetList(HeaderNames.SetCookie, value);
+            }
+        }
+
+        public T Get<T>(string name)
+        {
+            return Headers.Get<T>(name);
+        }
+
+        public IList<T> GetList<T>(string name)
+        {
+            return Headers.GetList<T>(name);
+        }
+
+        public void Set(string name, object value)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            Headers.Set(name, value);
+        }
+
+        public void SetList<T>(string name, IList<T> values)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            Headers.SetList<T>(name, values);
+        }
+
+        public void Append(string name, object value)
+        {
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            if (value == null)
+            {
+                throw new ArgumentNullException(nameof(value));
+            }
+
+            Headers.Append(name, value.ToString());
+        }
+
+        public void AppendList<T>(string name, IList<T> values)
+        {
+            Headers.AppendList<T>(name, values);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Extensions/src/SendFileResponseExtensions.cs b/src/Http/Http.Extensions/src/SendFileResponseExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..74c0422ef41f7f20c59721c1764a707e7754d42e
--- /dev/null
+++ b/src/Http/Http.Extensions/src/SendFileResponseExtensions.cs
@@ -0,0 +1,181 @@
+// 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;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Extensions;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.FileProviders;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Provides extensions for HttpResponse exposing the SendFile extension.
+    /// </summary>
+    public static class SendFileResponseExtensions
+    {
+        /// <summary>
+        /// Sends the given file using the SendFile extension.
+        /// </summary>
+        /// <param name="response"></param>
+        /// <param name="file">The file.</param>
+        /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
+        public static Task SendFileAsync(this HttpResponse response, IFileInfo file, CancellationToken cancellationToken = default)
+        {
+            if (response == null)
+            {
+                throw new ArgumentNullException(nameof(response));
+            }
+            if (file == null)
+            {
+                throw new ArgumentNullException(nameof(file));
+            }
+
+            return SendFileAsyncCore(response, file, 0, null, cancellationToken);
+        }
+
+        /// <summary>
+        /// Sends the given file using the SendFile extension.
+        /// </summary>
+        /// <param name="response"></param>
+        /// <param name="file">The file.</param>
+        /// <param name="offset">The offset in the file.</param>
+        /// <param name="count">The number of bytes to send, or null to send the remainder of the file.</param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public static Task SendFileAsync(this HttpResponse response, IFileInfo file, long offset, long? count, CancellationToken cancellationToken = default)
+        {
+            if (response == null)
+            {
+                throw new ArgumentNullException(nameof(response));
+            }
+            if (file == null)
+            {
+                throw new ArgumentNullException(nameof(file));
+            }
+
+            return SendFileAsyncCore(response, file, offset, count, cancellationToken);
+        }
+
+        /// <summary>
+        /// Sends the given file using the SendFile extension.
+        /// </summary>
+        /// <param name="response"></param>
+        /// <param name="fileName">The full path to the file.</param>
+        /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
+        /// <returns></returns>
+        public static Task SendFileAsync(this HttpResponse response, string fileName, CancellationToken cancellationToken = default)
+        {
+            if (response == null)
+            {
+                throw new ArgumentNullException(nameof(response));
+            }
+
+            if (fileName == null)
+            {
+                throw new ArgumentNullException(nameof(fileName));
+            }
+
+            return SendFileAsyncCore(response, fileName, 0, null, cancellationToken);
+        }
+
+        /// <summary>
+        /// Sends the given file using the SendFile extension.
+        /// </summary>
+        /// <param name="response"></param>
+        /// <param name="fileName">The full path to the file.</param>
+        /// <param name="offset">The offset in the file.</param>
+        /// <param name="count">The number of bytes to send, or null to send the remainder of the file.</param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public static Task SendFileAsync(this HttpResponse response, string fileName, long offset, long? count, CancellationToken cancellationToken = default)
+        {
+            if (response == null)
+            {
+                throw new ArgumentNullException(nameof(response));
+            }
+
+            if (fileName == null)
+            {
+                throw new ArgumentNullException(nameof(fileName));
+            }
+
+            return SendFileAsyncCore(response, fileName, offset, count, cancellationToken);
+        }
+
+        private static async Task SendFileAsyncCore(HttpResponse response, IFileInfo file, long offset, long? count, CancellationToken cancellationToken)
+        {
+            if (string.IsNullOrEmpty(file.PhysicalPath))
+            {
+                CheckRange(offset, count, file.Length);
+
+                using (var fileContent = file.CreateReadStream())
+                {
+                    if (offset > 0)
+                    {
+                        fileContent.Seek(offset, SeekOrigin.Begin);
+                    }
+                    await StreamCopyOperation.CopyToAsync(fileContent, response.Body, count, cancellationToken);
+                }
+            }
+            else
+            {
+                await response.SendFileAsync(file.PhysicalPath, offset, count, cancellationToken);
+            }
+        }
+
+        private static Task SendFileAsyncCore(HttpResponse response, string fileName, long offset, long? count, CancellationToken cancellationToken = default)
+        {
+            var sendFile = response.HttpContext.Features.Get<IHttpSendFileFeature>();
+            if (sendFile == null)
+            {
+                return SendFileAsyncCore(response.Body, fileName, offset, count, cancellationToken);
+            }
+
+            return sendFile.SendFileAsync(fileName, offset, count, cancellationToken);
+        }
+
+        // Not safe for overlapped writes.
+        private static async Task SendFileAsyncCore(Stream outputStream, string fileName, long offset, long? count, CancellationToken cancel = default)
+        {
+            cancel.ThrowIfCancellationRequested();
+
+            var fileInfo = new FileInfo(fileName);
+            CheckRange(offset, count, fileInfo.Length);
+
+            int bufferSize = 1024 * 16;
+            var fileStream = new FileStream(
+                fileName,
+                FileMode.Open,
+                FileAccess.Read,
+                FileShare.ReadWrite,
+                bufferSize: bufferSize,
+                options: FileOptions.Asynchronous | FileOptions.SequentialScan);
+
+            using (fileStream)
+            {
+                if (offset > 0)
+                {
+                    fileStream.Seek(offset, SeekOrigin.Begin);
+                }
+
+                await StreamCopyOperation.CopyToAsync(fileStream, outputStream, count, cancel);
+            }
+        }
+
+        private static void CheckRange(long offset, long? count, long fileLength)
+        {
+            if (offset < 0 || offset > fileLength)
+            {
+                throw new ArgumentOutOfRangeException(nameof(offset), offset, string.Empty);
+            }
+            if (count.HasValue &&
+                (count.Value < 0 || count.Value > fileLength - offset))
+            {
+                throw new ArgumentOutOfRangeException(nameof(count), count, string.Empty);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Extensions/src/SessionExtensions.cs b/src/Http/Http.Extensions/src/SessionExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..fd7573fa95db91d68b0620c27c48ea1186b162bc
--- /dev/null
+++ b/src/Http/Http.Extensions/src/SessionExtensions.cs
@@ -0,0 +1,54 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Text;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public static class SessionExtensions
+    {
+        public static void SetInt32(this ISession session, string key, int value)
+        {
+            var bytes = new byte[]
+            {
+                (byte)(value >> 24),
+                (byte)(0xFF & (value >> 16)),
+                (byte)(0xFF & (value >> 8)),
+                (byte)(0xFF & value)
+            };
+            session.Set(key, bytes);
+        }
+
+        public static int? GetInt32(this ISession session, string key)
+        {
+            var data = session.Get(key);
+            if (data == null || data.Length < 4)
+            {
+                return null;
+            }
+            return data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
+        }
+
+        public static void SetString(this ISession session, string key, string value)
+        {
+            session.Set(key, Encoding.UTF8.GetBytes(value));
+        }
+
+        public static string GetString(this ISession session, string key)
+        {
+            var data = session.Get(key);
+            if (data == null)
+            {
+                return null;
+            }
+            return Encoding.UTF8.GetString(data);
+        }
+
+        public static byte[] Get(this ISession session, string key)
+        {
+            byte[] value = null;
+            session.TryGetValue(key, out value);
+            return value;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Extensions/src/StreamCopyOperation.cs b/src/Http/Http.Extensions/src/StreamCopyOperation.cs
new file mode 100644
index 0000000000000000000000000000000000000000..12067fef6518d06713db6500380f7e7d84a51074
--- /dev/null
+++ b/src/Http/Http.Extensions/src/StreamCopyOperation.cs
@@ -0,0 +1,87 @@
+// 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.Buffers;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http.Extensions
+{
+    // FYI: In most cases the source will be a FileStream and the destination will be to the network.
+    public static class StreamCopyOperation
+    {
+        private const int DefaultBufferSize = 4096;
+
+        /// <summary>Asynchronously reads the bytes from the source stream and writes them to another stream.</summary>
+        /// <returns>A task that represents the asynchronous copy operation.</returns>
+        /// <param name="source">The stream from which the contents will be copied.</param>
+        /// <param name="destination">The stream to which the contents of the current stream will be copied.</param>
+        /// <param name="count">The count of bytes to be copied.</param>
+        /// <param name="cancel">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
+        public static Task CopyToAsync(Stream source, Stream destination, long? count, CancellationToken cancel)
+        {
+            return CopyToAsync(source, destination, count, DefaultBufferSize, cancel);
+        }
+
+        /// <summary>Asynchronously reads the bytes from the source stream and writes them to another stream, using a specified buffer size.</summary>
+        /// <returns>A task that represents the asynchronous copy operation.</returns>
+        /// <param name="source">The stream from which the contents will be copied.</param>
+        /// <param name="destination">The stream to which the contents of the current stream will be copied.</param>
+        /// <param name="count">The count of bytes to be copied.</param>
+        /// <param name="bufferSize">The size, in bytes, of the buffer. This value must be greater than zero. The default size is 4096.</param>
+        /// <param name="cancel">The token to monitor for cancellation requests. The default value is <see cref="P:System.Threading.CancellationToken.None" />.</param>
+        public static async Task CopyToAsync(Stream source, Stream destination, long? count, int bufferSize, CancellationToken cancel)
+        {
+            long? bytesRemaining = count;
+
+            var buffer = ArrayPool<byte>.Shared.Rent(bufferSize);
+            try
+            {
+                Debug.Assert(source != null);
+                Debug.Assert(destination != null);
+                Debug.Assert(!bytesRemaining.HasValue || bytesRemaining.Value >= 0);
+                Debug.Assert(buffer != null);
+
+                while (true)
+                {
+                    // The natural end of the range.
+                    if (bytesRemaining.HasValue && bytesRemaining.Value <= 0)
+                    {
+                        return;
+                    }
+
+                    cancel.ThrowIfCancellationRequested();
+
+                    int readLength = buffer.Length;
+                    if (bytesRemaining.HasValue)
+                    {
+                        readLength = (int)Math.Min(bytesRemaining.Value, (long)readLength);
+                    }
+                    int read = await source.ReadAsync(buffer, 0, readLength, cancel);
+
+                    if (bytesRemaining.HasValue)
+                    {
+                        bytesRemaining -= read;
+                    }
+
+                    // End of the source stream.
+                    if (read == 0)
+                    {
+                        return;
+                    }
+
+                    cancel.ThrowIfCancellationRequested();
+
+                    await destination.WriteAsync(buffer, 0, read, cancel);
+                }
+            }
+            finally
+            {
+                ArrayPool<byte>.Shared.Return(buffer);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Extensions/src/UriHelper.cs b/src/Http/Http.Extensions/src/UriHelper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..633e591186e73c30cbe04055c9581d566ed08329
--- /dev/null
+++ b/src/Http/Http.Extensions/src/UriHelper.cs
@@ -0,0 +1,217 @@
+// 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.Text;
+
+namespace Microsoft.AspNetCore.Http.Extensions
+{
+    /// <summary>
+    /// A helper class for constructing encoded Uris for use in headers and other Uris.
+    /// </summary>
+    public static class UriHelper
+    {
+        private const string ForwardSlash = "/";
+        private const string Pound = "#";
+        private const string QuestionMark = "?";
+        private const string SchemeDelimiter = "://";
+
+        /// <summary>
+        /// Combines the given URI components into a string that is properly encoded for use in HTTP headers.
+        /// </summary>
+        /// <param name="pathBase">The first portion of the request path associated with application root.</param>
+        /// <param name="path">The portion of the request path that identifies the requested resource.</param>
+        /// <param name="query">The query, if any.</param>
+        /// <param name="fragment">The fragment, if any.</param>
+        /// <returns></returns>
+        public static string BuildRelative(
+            PathString pathBase = new PathString(),
+            PathString path = new PathString(),
+            QueryString query = new QueryString(),
+            FragmentString fragment = new FragmentString())
+        {
+            string combinePath = (pathBase.HasValue || path.HasValue) ? (pathBase + path).ToString() : "/";
+            return combinePath + query.ToString() + fragment.ToString();
+        }
+
+        /// <summary>
+        /// Combines the given URI components into a string that is properly encoded for use in HTTP headers.
+        /// Note that unicode in the HostString will be encoded as punycode.
+        /// </summary>
+        /// <param name="scheme">http, https, etc.</param>
+        /// <param name="host">The host portion of the uri normally included in the Host header. This may include the port.</param>
+        /// <param name="pathBase">The first portion of the request path associated with application root.</param>
+        /// <param name="path">The portion of the request path that identifies the requested resource.</param>
+        /// <param name="query">The query, if any.</param>
+        /// <param name="fragment">The fragment, if any.</param>
+        /// <returns></returns>
+        public static string BuildAbsolute(
+            string scheme,
+            HostString host,
+            PathString pathBase = new PathString(),
+            PathString path = new PathString(),
+            QueryString query = new QueryString(),
+            FragmentString fragment = new FragmentString())
+        {
+            if (scheme == null)
+            {
+                throw new ArgumentNullException(nameof(scheme));
+            }
+
+            var combinedPath = (pathBase.HasValue || path.HasValue) ? (pathBase + path).ToString() : "/";
+
+            var encodedHost = host.ToString();
+            var encodedQuery = query.ToString();
+            var encodedFragment = fragment.ToString();
+
+            // PERF: Calculate string length to allocate correct buffer size for StringBuilder.
+            var length = scheme.Length + SchemeDelimiter.Length + encodedHost.Length
+                + combinedPath.Length + encodedQuery.Length + encodedFragment.Length;
+
+            return new StringBuilder(length)
+                .Append(scheme)
+                .Append(SchemeDelimiter)
+                .Append(encodedHost)
+                .Append(combinedPath)
+                .Append(encodedQuery)
+                .Append(encodedFragment)
+                .ToString();
+        }
+
+        /// <summary>
+        /// Separates the given absolute URI string into components. Assumes no PathBase.
+        /// </summary>
+        /// <param name="uri">A string representation of the uri.</param>
+        /// <param name="scheme">http, https, etc.</param>
+        /// <param name="host">The host portion of the uri normally included in the Host header. This may include the port.</param>
+        /// <param name="path">The portion of the request path that identifies the requested resource.</param>
+        /// <param name="query">The query, if any.</param>
+        /// <param name="fragment">The fragment, if any.</param>
+        public static void FromAbsolute(
+            string uri,
+            out string scheme,
+            out HostString host, 
+            out PathString path,
+            out QueryString query, 
+            out FragmentString fragment)
+        {
+            if (uri == null)
+            {
+                throw new ArgumentNullException(nameof(uri));
+            }
+
+            path = new PathString();
+            query = new QueryString();
+            fragment = new FragmentString();
+            var startIndex = uri.IndexOf(SchemeDelimiter);
+
+            if (startIndex < 0)
+            {
+                throw new FormatException("No scheme delimiter in uri.");
+            }
+
+            scheme = uri.Substring(0, startIndex);
+
+            // PERF: Calculate the end of the scheme for next IndexOf
+            startIndex += SchemeDelimiter.Length;
+
+            var searchIndex = -1;
+            var limit = uri.Length;
+
+            if ((searchIndex = uri.IndexOf(Pound, startIndex)) >= 0 && searchIndex < limit)
+            {
+                fragment = FragmentString.FromUriComponent(uri.Substring(searchIndex));
+                limit = searchIndex;
+            }
+
+            if ((searchIndex = uri.IndexOf(QuestionMark, startIndex)) >= 0 && searchIndex < limit)
+            {
+                query = QueryString.FromUriComponent(uri.Substring(searchIndex, limit - searchIndex));
+                limit = searchIndex;
+            }
+
+            if ((searchIndex = uri.IndexOf(ForwardSlash, startIndex)) >= 0 && searchIndex < limit)
+            {
+                path = PathString.FromUriComponent(uri.Substring(searchIndex, limit - searchIndex));
+                limit = searchIndex;
+            }
+            
+            host = HostString.FromUriComponent(uri.Substring(startIndex, limit - startIndex));
+        }
+
+        /// <summary>
+        /// Generates a string from the given absolute or relative Uri that is appropriately encoded for use in
+        /// HTTP headers. Note that a unicode host name will be encoded as punycode.
+        /// </summary>
+        /// <param name="uri">The Uri to encode.</param>
+        /// <returns></returns>
+        public static string Encode(Uri uri)
+        {
+            if (uri == null)
+            {
+                throw new ArgumentNullException(nameof(uri));
+            }
+
+            if (uri.IsAbsoluteUri)
+            {
+                return BuildAbsolute(
+                    scheme: uri.Scheme,
+                    host: HostString.FromUriComponent(uri),
+                    pathBase: PathString.FromUriComponent(uri),
+                    query: QueryString.FromUriComponent(uri),
+                    fragment: FragmentString.FromUriComponent(uri));
+            }
+            else
+            {
+                return uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
+            }
+        }
+
+        /// <summary>
+        /// Returns the combined components of the request URL in a fully escaped form suitable for use in HTTP headers
+        /// and other HTTP operations.
+        /// </summary>
+        /// <param name="request">The request to assemble the uri pieces from.</param>
+        /// <returns></returns>
+        public static string GetEncodedUrl(this HttpRequest request)
+        {
+            return BuildAbsolute(request.Scheme, request.Host, request.PathBase, request.Path, request.QueryString);
+        }
+        /// <summary>
+        /// Returns the relative url 
+        /// </summary>
+        /// <param name="request">The request to assemble the uri pieces from.</param>
+        /// <returns></returns>
+        public static string GetEncodedPathAndQuery(this HttpRequest request)
+        {
+            return BuildRelative(request.PathBase, request.Path, request.QueryString);
+        }
+
+        /// <summary>
+        /// Returns the combined components of the request URL in a fully un-escaped form (except for the QueryString)
+        /// suitable only for display. This format should not be used in HTTP headers or other HTTP operations.
+        /// </summary>
+        /// <param name="request">The request to assemble the uri pieces from.</param>
+        /// <returns></returns>
+        public static string GetDisplayUrl(this HttpRequest request)
+        {
+            var host = request.Host.Value;
+            var pathBase = request.PathBase.Value;
+            var path = request.Path.Value;
+            var queryString = request.QueryString.Value;
+
+            // PERF: Calculate string length to allocate correct buffer size for StringBuilder.
+            var length = request.Scheme.Length + SchemeDelimiter.Length + host.Length
+                + pathBase.Length + path.Length + queryString.Length;
+
+            return new StringBuilder(length)
+                .Append(request.Scheme)
+                .Append(SchemeDelimiter)
+                .Append(host)
+                .Append(pathBase)
+                .Append(path)
+                .Append(queryString)
+                .ToString();
+        }
+    }
+}
diff --git a/src/Http/Http.Extensions/src/baseline.netcore.json b/src/Http/Http.Extensions/src/baseline.netcore.json
new file mode 100644
index 0000000000000000000000000000000000000000..286133ea54d26159149cb9d7ebdb30252be1186d
--- /dev/null
+++ b/src/Http/Http.Extensions/src/baseline.netcore.json
@@ -0,0 +1,1699 @@
+{
+  "AssemblyIdentity": "Microsoft.AspNetCore.Http.Extensions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+  "Types": [
+    {
+      "Name": "Microsoft.AspNetCore.Http.HeaderDictionaryTypeExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "GetTypedHeaders",
+          "Parameters": [
+            {
+              "Name": "request",
+              "Type": "Microsoft.AspNetCore.Http.HttpRequest"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.Headers.RequestHeaders",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetTypedHeaders",
+          "Parameters": [
+            {
+              "Name": "response",
+              "Type": "Microsoft.AspNetCore.Http.HttpResponse"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.Headers.ResponseHeaders",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AppendList<T0>",
+          "Parameters": [
+            {
+              "Name": "Headers",
+              "Type": "Microsoft.AspNetCore.Http.IHeaderDictionary"
+            },
+            {
+              "Name": "name",
+              "Type": "System.String"
+            },
+            {
+              "Name": "values",
+              "Type": "System.Collections.Generic.IList<T0>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "T",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.ResponseExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Clear",
+          "Parameters": [
+            {
+              "Name": "response",
+              "Type": "Microsoft.AspNetCore.Http.HttpResponse"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.SendFileResponseExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "SendFileAsync",
+          "Parameters": [
+            {
+              "Name": "response",
+              "Type": "Microsoft.AspNetCore.Http.HttpResponse"
+            },
+            {
+              "Name": "file",
+              "Type": "Microsoft.Extensions.FileProviders.IFileInfo"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SendFileAsync",
+          "Parameters": [
+            {
+              "Name": "response",
+              "Type": "Microsoft.AspNetCore.Http.HttpResponse"
+            },
+            {
+              "Name": "file",
+              "Type": "Microsoft.Extensions.FileProviders.IFileInfo"
+            },
+            {
+              "Name": "offset",
+              "Type": "System.Int64"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Nullable<System.Int64>"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SendFileAsync",
+          "Parameters": [
+            {
+              "Name": "response",
+              "Type": "Microsoft.AspNetCore.Http.HttpResponse"
+            },
+            {
+              "Name": "fileName",
+              "Type": "System.String"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SendFileAsync",
+          "Parameters": [
+            {
+              "Name": "response",
+              "Type": "Microsoft.AspNetCore.Http.HttpResponse"
+            },
+            {
+              "Name": "fileName",
+              "Type": "System.String"
+            },
+            {
+              "Name": "offset",
+              "Type": "System.Int64"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Nullable<System.Int64>"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.SessionExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "SetInt32",
+          "Parameters": [
+            {
+              "Name": "session",
+              "Type": "Microsoft.AspNetCore.Http.ISession"
+            },
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetInt32",
+          "Parameters": [
+            {
+              "Name": "session",
+              "Type": "Microsoft.AspNetCore.Http.ISession"
+            },
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Nullable<System.Int32>",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SetString",
+          "Parameters": [
+            {
+              "Name": "session",
+              "Type": "Microsoft.AspNetCore.Http.ISession"
+            },
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetString",
+          "Parameters": [
+            {
+              "Name": "session",
+              "Type": "Microsoft.AspNetCore.Http.ISession"
+            },
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Get",
+          "Parameters": [
+            {
+              "Name": "session",
+              "Type": "Microsoft.AspNetCore.Http.ISession"
+            },
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Byte[]",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Headers.RequestHeaders",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Headers",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IHeaderDictionary",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Accept",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.MediaTypeHeaderValue>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Accept",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.MediaTypeHeaderValue>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_AcceptCharset",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.StringWithQualityHeaderValue>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_AcceptCharset",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.StringWithQualityHeaderValue>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_AcceptEncoding",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.StringWithQualityHeaderValue>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_AcceptEncoding",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.StringWithQualityHeaderValue>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_AcceptLanguage",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.StringWithQualityHeaderValue>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_AcceptLanguage",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.StringWithQualityHeaderValue>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_CacheControl",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.CacheControlHeaderValue",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_CacheControl",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Net.Http.Headers.CacheControlHeaderValue"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentDisposition",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.ContentDispositionHeaderValue",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentDisposition",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Net.Http.Headers.ContentDispositionHeaderValue"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentLength",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Int64>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentLength",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.Int64>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentRange",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.ContentRangeHeaderValue",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentRange",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Net.Http.Headers.ContentRangeHeaderValue"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentType",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentType",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Cookie",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.CookieHeaderValue>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Cookie",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.CookieHeaderValue>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Date",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Date",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.DateTimeOffset>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Expires",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Expires",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.DateTimeOffset>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Host",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.HostString",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Host",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.HostString"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IfMatch",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.EntityTagHeaderValue>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_IfMatch",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.EntityTagHeaderValue>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IfModifiedSince",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_IfModifiedSince",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.DateTimeOffset>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IfNoneMatch",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.EntityTagHeaderValue>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_IfNoneMatch",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.EntityTagHeaderValue>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IfRange",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.RangeConditionHeaderValue",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_IfRange",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Net.Http.Headers.RangeConditionHeaderValue"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IfUnmodifiedSince",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_IfUnmodifiedSince",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.DateTimeOffset>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_LastModified",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_LastModified",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.DateTimeOffset>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Range",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.RangeHeaderValue",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Range",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Net.Http.Headers.RangeHeaderValue"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Referer",
+          "Parameters": [],
+          "ReturnType": "System.Uri",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Referer",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Uri"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Get<T0>",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "T0",
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "T",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetList<T0>",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.IList<T0>",
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "T",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Method",
+          "Name": "Set",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SetList<T0>",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            },
+            {
+              "Name": "values",
+              "Type": "System.Collections.Generic.IList<T0>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "T",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Method",
+          "Name": "Append",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AppendList<T0>",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            },
+            {
+              "Name": "values",
+              "Type": "System.Collections.Generic.IList<T0>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "T",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "headers",
+              "Type": "Microsoft.AspNetCore.Http.IHeaderDictionary"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Headers.ResponseHeaders",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Headers",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IHeaderDictionary",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_CacheControl",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.CacheControlHeaderValue",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_CacheControl",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Net.Http.Headers.CacheControlHeaderValue"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentDisposition",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.ContentDispositionHeaderValue",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentDisposition",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Net.Http.Headers.ContentDispositionHeaderValue"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentLength",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Int64>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentLength",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.Int64>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentRange",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.ContentRangeHeaderValue",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentRange",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Net.Http.Headers.ContentRangeHeaderValue"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentType",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentType",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Date",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Date",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.DateTimeOffset>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ETag",
+          "Parameters": [],
+          "ReturnType": "Microsoft.Net.Http.Headers.EntityTagHeaderValue",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ETag",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Expires",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Expires",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.DateTimeOffset>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_LastModified",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_LastModified",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.DateTimeOffset>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Location",
+          "Parameters": [],
+          "ReturnType": "System.Uri",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Location",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Uri"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_SetCookie",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.SetCookieHeaderValue>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_SetCookie",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Collections.Generic.IList<Microsoft.Net.Http.Headers.SetCookieHeaderValue>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Get<T0>",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "T0",
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "T",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetList<T0>",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.IList<T0>",
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "T",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Method",
+          "Name": "Set",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SetList<T0>",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            },
+            {
+              "Name": "values",
+              "Type": "System.Collections.Generic.IList<T0>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "T",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Method",
+          "Name": "Append",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AppendList<T0>",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            },
+            {
+              "Name": "values",
+              "Type": "System.Collections.Generic.IList<T0>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "T",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "headers",
+              "Type": "Microsoft.AspNetCore.Http.IHeaderDictionary"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Extensions.HttpRequestMultipartExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "GetMultipartBoundary",
+          "Parameters": [
+            {
+              "Name": "request",
+              "Type": "Microsoft.AspNetCore.Http.HttpRequest"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Extensions.QueryBuilder",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.String, System.String>>"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Add",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "values",
+              "Type": "System.Collections.Generic.IEnumerable<System.String>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Add",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ToQueryString",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.QueryString",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetHashCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Equals",
+          "Parameters": [
+            {
+              "Name": "obj",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetEnumerator",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<System.String, System.String>>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.String, System.String>>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "parameters",
+              "Type": "System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.String, System.String>>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Extensions.StreamCopyOperation",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "CopyToAsync",
+          "Parameters": [
+            {
+              "Name": "source",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "destination",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Nullable<System.Int64>"
+            },
+            {
+              "Name": "cancel",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "CopyToAsync",
+          "Parameters": [
+            {
+              "Name": "source",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "destination",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Nullable<System.Int64>"
+            },
+            {
+              "Name": "bufferSize",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "cancel",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Extensions.UriHelper",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "BuildRelative",
+          "Parameters": [
+            {
+              "Name": "pathBase",
+              "Type": "Microsoft.AspNetCore.Http.PathString",
+              "DefaultValue": "default(Microsoft.AspNetCore.Http.PathString)"
+            },
+            {
+              "Name": "path",
+              "Type": "Microsoft.AspNetCore.Http.PathString",
+              "DefaultValue": "default(Microsoft.AspNetCore.Http.PathString)"
+            },
+            {
+              "Name": "query",
+              "Type": "Microsoft.AspNetCore.Http.QueryString",
+              "DefaultValue": "default(Microsoft.AspNetCore.Http.QueryString)"
+            },
+            {
+              "Name": "fragment",
+              "Type": "Microsoft.AspNetCore.Http.FragmentString",
+              "DefaultValue": "default(Microsoft.AspNetCore.Http.FragmentString)"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "BuildAbsolute",
+          "Parameters": [
+            {
+              "Name": "scheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "host",
+              "Type": "Microsoft.AspNetCore.Http.HostString"
+            },
+            {
+              "Name": "pathBase",
+              "Type": "Microsoft.AspNetCore.Http.PathString",
+              "DefaultValue": "default(Microsoft.AspNetCore.Http.PathString)"
+            },
+            {
+              "Name": "path",
+              "Type": "Microsoft.AspNetCore.Http.PathString",
+              "DefaultValue": "default(Microsoft.AspNetCore.Http.PathString)"
+            },
+            {
+              "Name": "query",
+              "Type": "Microsoft.AspNetCore.Http.QueryString",
+              "DefaultValue": "default(Microsoft.AspNetCore.Http.QueryString)"
+            },
+            {
+              "Name": "fragment",
+              "Type": "Microsoft.AspNetCore.Http.FragmentString",
+              "DefaultValue": "default(Microsoft.AspNetCore.Http.FragmentString)"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "FromAbsolute",
+          "Parameters": [
+            {
+              "Name": "uri",
+              "Type": "System.String"
+            },
+            {
+              "Name": "scheme",
+              "Type": "System.String",
+              "Direction": "Out"
+            },
+            {
+              "Name": "host",
+              "Type": "Microsoft.AspNetCore.Http.HostString",
+              "Direction": "Out"
+            },
+            {
+              "Name": "path",
+              "Type": "Microsoft.AspNetCore.Http.PathString",
+              "Direction": "Out"
+            },
+            {
+              "Name": "query",
+              "Type": "Microsoft.AspNetCore.Http.QueryString",
+              "Direction": "Out"
+            },
+            {
+              "Name": "fragment",
+              "Type": "Microsoft.AspNetCore.Http.FragmentString",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Encode",
+          "Parameters": [
+            {
+              "Name": "uri",
+              "Type": "System.Uri"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetEncodedUrl",
+          "Parameters": [
+            {
+              "Name": "request",
+              "Type": "Microsoft.AspNetCore.Http.HttpRequest"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetEncodedPathAndQuery",
+          "Parameters": [
+            {
+              "Name": "request",
+              "Type": "Microsoft.AspNetCore.Http.HttpRequest"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetDisplayUrl",
+          "Parameters": [
+            {
+              "Name": "request",
+              "Type": "Microsoft.AspNetCore.Http.HttpRequest"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/Http/Http.Extensions/test/HeaderDictionaryTypeExtensionsTest.cs b/src/Http/Http.Extensions/test/HeaderDictionaryTypeExtensionsTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1d01466284a7d9b2aeecb370e247273f3e6f81e4
--- /dev/null
+++ b/src/Http/Http.Extensions/test/HeaderDictionaryTypeExtensionsTest.cs
@@ -0,0 +1,205 @@
+// 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 Microsoft.Net.Http.Headers;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Headers
+{
+    public class HeaderDictionaryTypeExtensionsTest
+    {
+        [Fact]
+        public void GetT_KnownTypeWithValidValue_Success()
+        {
+            var context = new DefaultHttpContext();
+            context.Request.Headers[HeaderNames.ContentType] = "text/plain";
+
+            var result = context.Request.GetTypedHeaders().Get<MediaTypeHeaderValue>(HeaderNames.ContentType);
+
+            var expected = new MediaTypeHeaderValue("text/plain");
+            Assert.Equal(expected, result);
+        }
+
+        [Fact]
+        public void GetT_KnownTypeWithMissingValue_Null()
+        {
+            var context = new DefaultHttpContext();
+
+            var result = context.Request.GetTypedHeaders().Get<MediaTypeHeaderValue>(HeaderNames.ContentType);
+
+            Assert.Null(result);
+        }
+
+        [Fact]
+        public void GetT_KnownTypeWithInvalidValue_Null()
+        {
+            var context = new DefaultHttpContext();
+            context.Request.Headers[HeaderNames.ContentType] = "invalid";
+
+            var result = context.Request.GetTypedHeaders().Get<MediaTypeHeaderValue>(HeaderNames.ContentType);
+
+            Assert.Null(result);
+        }
+
+        [Fact]
+        public void GetT_UnknownTypeWithTryParseAndValidValue_Success()
+        {
+            var context = new DefaultHttpContext();
+            context.Request.Headers["custom"] = "valid";
+
+            var result = context.Request.GetTypedHeaders().Get<TestHeaderValue>("custom");
+            Assert.NotNull(result);
+        }
+
+        [Fact]
+        public void GetT_UnknownTypeWithTryParseAndInvalidValue_Null()
+        {
+            var context = new DefaultHttpContext();
+            context.Request.Headers["custom"] = "invalid";
+
+            var result = context.Request.GetTypedHeaders().Get<TestHeaderValue>("custom");
+            Assert.Null(result);
+        }
+
+        [Fact]
+        public void GetT_UnknownTypeWithTryParseAndMissingValue_Null()
+        {
+            var context = new DefaultHttpContext();
+
+            var result = context.Request.GetTypedHeaders().Get<TestHeaderValue>("custom");
+            Assert.Null(result);
+        }
+
+        [Fact]
+        public void GetT_UnknownTypeWithoutTryParse_Throws()
+        {
+            var context = new DefaultHttpContext();
+            context.Request.Headers["custom"] = "valid";
+
+            Assert.Throws<NotSupportedException>(() => context.Request.GetTypedHeaders().Get<object>("custom"));
+        }
+
+        [Fact]
+        public void GetListT_KnownTypeWithValidValue_Success()
+        {
+            var context = new DefaultHttpContext();
+            context.Request.Headers[HeaderNames.Accept] = "text/plain; q=0.9, text/other, */*";
+
+            var result = context.Request.GetTypedHeaders().GetList<MediaTypeHeaderValue>(HeaderNames.Accept);
+
+            var expected = new[] {
+                new MediaTypeHeaderValue("text/plain", 0.9),
+                new MediaTypeHeaderValue("text/other"),
+                new MediaTypeHeaderValue("*/*"),
+            }.ToList();
+            Assert.Equal(expected, result);
+        }
+
+        [Fact]
+        public void GetListT_KnownTypeWithMissingValue_Null()
+        {
+            var context = new DefaultHttpContext();
+
+            var result = context.Request.GetTypedHeaders().GetList<MediaTypeHeaderValue>(HeaderNames.Accept);
+
+            Assert.Null(result);
+        }
+
+        [Fact]
+        public void GetListT_KnownTypeWithInvalidValue_Null()
+        {
+            var context = new DefaultHttpContext();
+            context.Request.Headers[HeaderNames.Accept] = "invalid";
+
+            var result = context.Request.GetTypedHeaders().GetList<MediaTypeHeaderValue>(HeaderNames.Accept);
+
+            Assert.Null(result);
+        }
+
+        [Fact]
+        public void GetListT_UnknownTypeWithTryParseListAndValidValue_Success()
+        {
+            var context = new DefaultHttpContext();
+            context.Request.Headers["custom"] = "valid";
+
+            var results = context.Request.GetTypedHeaders().GetList<TestHeaderValue>("custom");
+            Assert.NotNull(results);
+            Assert.Equal(new[] { new TestHeaderValue() }.ToList(), results);
+        }
+
+        [Fact]
+        public void GetListT_UnknownTypeWithTryParseListAndInvalidValue_Null()
+        {
+            var context = new DefaultHttpContext();
+            context.Request.Headers["custom"] = "invalid";
+
+            var results = context.Request.GetTypedHeaders().GetList<TestHeaderValue>("custom");
+            Assert.Null(results);
+        }
+
+        [Fact]
+        public void GetListT_UnknownTypeWithTryParseListAndMissingValue_Null()
+        {
+            var context = new DefaultHttpContext();
+
+            var results = context.Request.GetTypedHeaders().GetList<TestHeaderValue>("custom");
+            Assert.Null(results);
+        }
+
+        [Fact]
+        public void GetListT_UnknownTypeWithoutTryParseList_Throws()
+        {
+            var context = new DefaultHttpContext();
+            context.Request.Headers["custom"] = "valid";
+
+            Assert.Throws<NotSupportedException>(() => context.Request.GetTypedHeaders().GetList<object>("custom"));
+        }
+
+        public class TestHeaderValue
+        {
+            public static bool TryParse(string value, out TestHeaderValue result)
+            {
+                if (string.Equals("valid", value))
+                {
+                    result = new TestHeaderValue();
+                    return true;
+                }
+                result = null;
+                return false;
+            }
+
+            public static bool TryParseList(IList<string> values, out IList<TestHeaderValue> result)
+            {
+                var results = new List<TestHeaderValue>();
+                foreach (var value in values)
+                {
+                    if (string.Equals("valid", value))
+                    {
+                        results.Add(new TestHeaderValue());
+                    }
+                }
+                if (results.Count > 0)
+                {
+                    result = results;
+                    return true;
+                }
+                result = null;
+                return false;
+            }
+
+            public override bool Equals(object obj)
+            {
+                var other = obj as TestHeaderValue;
+                return other != null;
+            }
+
+            public override int GetHashCode()
+            {
+                return 0;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj b/src/Http/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..fae14d9f7aacab41df367362573bffd65314d6dd
--- /dev/null
+++ b/src/Http/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Http" />
+    <Reference Include="Microsoft.AspNetCore.Http.Extensions" />
+    <Reference Include="Microsoft.Extensions.DependencyInjection" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/Http.Extensions/test/QueryBuilderTests.cs b/src/Http/Http.Extensions/test/QueryBuilderTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7d15dd87bfe01520ca7b3e35fb17551005dd54f7
--- /dev/null
+++ b/src/Http/Http.Extensions/test/QueryBuilderTests.cs
@@ -0,0 +1,98 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Http.Extensions
+{
+    public class QueryBuilderTests
+    {
+        [Fact]
+        public void EmptyQuery_NoQuestionMark()
+        {
+            var builder = new QueryBuilder();
+            Assert.Equal(string.Empty, builder.ToString());
+        }
+
+        [Fact]
+        public void AddSimple_NoEncoding()
+        {
+            var builder = new QueryBuilder();
+            builder.Add("key", "value");
+            Assert.Equal("?key=value", builder.ToString());
+        }
+
+        [Fact]
+        public void AddSpace_PercentEncoded()
+        {
+            var builder = new QueryBuilder();
+            builder.Add("key", "value 1");
+            Assert.Equal("?key=value%201", builder.ToString());
+        }
+
+        [Fact]
+        public void AddReservedCharacters_PercentEncoded()
+        {
+            var builder = new QueryBuilder();
+            builder.Add("key&", "value#");
+            Assert.Equal("?key%26=value%23", builder.ToString());
+        }
+
+        [Fact]
+        public void AddMultipleValues_AddedInOrder()
+        {
+            var builder = new QueryBuilder();
+            builder.Add("key1", "value1");
+            builder.Add("key2", "value2");
+            builder.Add("key3", "value3");
+            Assert.Equal("?key1=value1&key2=value2&key3=value3", builder.ToString());
+        }
+
+        [Fact]
+        public void AddIEnumerableValues_AddedInOrder()
+        {
+            var builder = new QueryBuilder();
+            builder.Add("key", new[] { "value1", "value2", "value3" });
+            Assert.Equal("?key=value1&key=value2&key=value3", builder.ToString());
+        }
+
+        [Fact]
+        public void AddMultipleValuesViaConstructor_AddedInOrder()
+        {
+            var builder = new QueryBuilder(new[]
+            {
+                new KeyValuePair<string, string>("key1", "value1"),
+                new KeyValuePair<string, string>("key2", "value2"),
+                new KeyValuePair<string, string>("key3", "value3"),
+            });
+            Assert.Equal("?key1=value1&key2=value2&key3=value3", builder.ToString());
+        }
+
+        [Fact]
+        public void AddMultipleValuesViaInitializer_AddedInOrder()
+        {
+            var builder = new QueryBuilder()
+            {
+                { "key1", "value1" },
+                { "key2", "value2" },
+                { "key3", "value3" },
+            };
+            Assert.Equal("?key1=value1&key2=value2&key3=value3", builder.ToString());
+        }
+
+        [Fact]
+        public void CopyViaConstructor_AddedInOrder()
+        {
+            var builder = new QueryBuilder()
+            {
+                { "key1", "value1" },
+                { "key2", "value2" },
+                { "key3", "value3" },
+            };
+            var builder1 = new QueryBuilder(builder);
+            Assert.Equal("?key1=value1&key2=value2&key3=value3", builder1.ToString());
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Extensions/test/ResponseExtensionTests.cs b/src/Http/Http.Extensions/test/ResponseExtensionTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ae6b147fd2231b56cab941a351eb16f9d9e3d28c
--- /dev/null
+++ b/src/Http/Http.Extensions/test/ResponseExtensionTests.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.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Extensions
+{
+    public class ResponseExtensionTests
+    {
+        [Fact]
+        public void Clear_ResetsResponse()
+        {
+            var context = new DefaultHttpContext();
+            context.Response.StatusCode = 201;
+            context.Response.Headers["custom"] = "value";
+            context.Response.Body.Write(new byte[100], 0, 100);
+
+            context.Response.Clear();
+
+            Assert.Equal(200, context.Response.StatusCode);
+            Assert.Equal(string.Empty, context.Response.Headers["custom"].ToString());
+            Assert.Equal(0, context.Response.Body.Length);
+        }
+
+        [Fact]
+        public void Clear_AlreadyStarted_Throws()
+        {
+            var context = new DefaultHttpContext();
+            context.Features.Set<IHttpResponseFeature>(new StartedResponseFeature());
+
+            Assert.Throws<InvalidOperationException>(() => context.Response.Clear());
+        }
+
+        private class StartedResponseFeature : IHttpResponseFeature
+        {
+            public Stream Body { get; set; }
+
+            public bool HasStarted { get { return true; } }
+
+            public IHeaderDictionary Headers { get; set; }
+
+            public string ReasonPhrase { get; set; }
+
+            public int StatusCode { get; set; }
+
+            public void OnCompleted(Func<object, Task> callback, object state)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void OnStarting(Func<object, Task> callback, object state)
+            {
+                throw new NotImplementedException();
+            }
+        }
+    }
+}
diff --git a/src/Http/Http.Extensions/test/SendFileResponseExtensionsTests.cs b/src/Http/Http.Extensions/test/SendFileResponseExtensionsTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f4c7c0f2a9918a84d1a1283a75d57bc7c6b14fac
--- /dev/null
+++ b/src/Http/Http.Extensions/test/SendFileResponseExtensionsTests.cs
@@ -0,0 +1,53 @@
+// Copyright (c) .NET Foundation. All rights reserved. See License.txt in the project root for license information.
+
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Extensions.Tests
+{
+    public class SendFileResponseExtensionsTests
+    {
+        [Fact]
+        public Task SendFileWhenFileNotFoundThrows()
+        {
+            var response = new DefaultHttpContext().Response;
+            return Assert.ThrowsAsync<FileNotFoundException>(() => response.SendFileAsync("foo"));
+        }
+
+        [Fact]
+        public async Task SendFileWorks()
+        {
+            var context = new DefaultHttpContext();
+            var response = context.Response;
+            var fakeFeature = new FakeSendFileFeature();
+            context.Features.Set<IHttpSendFileFeature>(fakeFeature);
+
+            await response.SendFileAsync("bob", 1, 3, CancellationToken.None);
+
+            Assert.Equal("bob", fakeFeature.name);
+            Assert.Equal(1, fakeFeature.offset);
+            Assert.Equal(3, fakeFeature.length);
+            Assert.Equal(CancellationToken.None, fakeFeature.token);
+        }
+
+        private class FakeSendFileFeature : IHttpSendFileFeature
+        {
+            public string name = null;
+            public long offset = 0;
+            public long? length = null;
+            public CancellationToken token;
+
+            public Task SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
+            {
+                this.name = path;
+                this.offset = offset;
+                this.length = length;
+                this.token = cancellation;
+                return Task.FromResult(0);
+            }
+        }
+    }
+}
diff --git a/src/Http/Http.Extensions/test/UriHelperTests.cs b/src/Http/Http.Extensions/test/UriHelperTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..11b045af4fdeb885c53b629bd2037fb0fdeefea4
--- /dev/null
+++ b/src/Http/Http.Extensions/test/UriHelperTests.cs
@@ -0,0 +1,156 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Extensions
+{
+    public class UriHelperTests
+    {
+        [Fact]
+        public void EncodeEmptyPartialUrl()
+        {
+            var result = UriHelper.BuildRelative();
+
+            Assert.Equal("/", result);
+        }
+
+        [Fact]
+        public void EncodePartialUrl()
+        {
+            var result = UriHelper.BuildRelative(new PathString("/un?escaped/base"), new PathString("/un?escaped"),
+                new QueryString("?name=val%23ue"), new FragmentString("#my%20value"));
+
+            Assert.Equal("/un%3Fescaped/base/un%3Fescaped?name=val%23ue#my%20value", result);
+        }
+
+        [Fact]
+        public void EncodeEmptyFullUrl()
+        {
+            var result = UriHelper.BuildAbsolute("http", new HostString(string.Empty));
+
+            Assert.Equal("http:///", result);
+        }
+
+        [Fact]
+        public void EncodeFullUrl()
+        {
+            var result = UriHelper.BuildAbsolute("http", new HostString("my.HoΨst:80"), new PathString("/un?escaped/base"), new PathString("/un?escaped"),
+                new QueryString("?name=val%23ue"), new FragmentString("#my%20value"));
+
+            Assert.Equal("http://my.xn--host-cpd:80/un%3Fescaped/base/un%3Fescaped?name=val%23ue#my%20value", result);
+        }
+
+        [Fact]
+        public void GetEncodedUrlFromRequest()
+        {
+            var request = new DefaultHttpContext().Request;
+            request.Scheme = "http";
+            request.Host = new HostString("my.HoΨst:80");
+            request.PathBase = new PathString("/un?escaped/base");
+            request.Path = new PathString("/un?escaped");
+            request.QueryString = new QueryString("?name=val%23ue");
+
+            Assert.Equal("http://my.xn--host-cpd:80/un%3Fescaped/base/un%3Fescaped?name=val%23ue", request.GetEncodedUrl());
+        }
+
+        [Fact]
+        public void GetDisplayUrlFromRequest()
+        {
+            var request = new DefaultHttpContext().Request;
+            request.Scheme = "http";
+            request.Host = new HostString("my.HoΨst:80");
+            request.PathBase = new PathString("/un?escaped/base");
+            request.Path = new PathString("/un?escaped");
+            request.QueryString = new QueryString("?name=val%23ue");
+
+            Assert.Equal("http://my.hoψst:80/un?escaped/base/un?escaped?name=val%23ue", request.GetDisplayUrl());
+        }
+
+        [Theory]
+        [InlineData("http://example.com", "http", "example.com", "", "", "")]
+        [InlineData("https://example.com", "https", "example.com", "", "", "")]
+        [InlineData("http://example.com/foo/bar", "http", "example.com", "/foo/bar", "", "")]
+        [InlineData("http://example.com/foo/bar?baz=1", "http", "example.com", "/foo/bar", "?baz=1", "")]
+        [InlineData("http://example.com/foo#col=2", "http", "example.com", "/foo", "", "#col=2")]
+        [InlineData("http://example.com/foo?bar=1#col=2", "http", "example.com", "/foo", "?bar=1", "#col=2")]
+        [InlineData("http://example.com?bar=1#col=2", "http", "example.com", "", "?bar=1", "#col=2")]
+        [InlineData("http://example.com#frag?stillfrag/stillfrag", "http", "example.com", "", "", "#frag?stillfrag/stillfrag")]
+        [InlineData("http://example.com?q/stillq#frag?stillfrag/stillfrag", "http", "example.com", "", "?q/stillq", "#frag?stillfrag/stillfrag")]
+        [InlineData("http://example.com/fo%23o#col=2", "http", "example.com", "/fo#o", "", "#col=2")]
+        [InlineData("http://example.com/fo%3Fo#col=2", "http", "example.com", "/fo?o", "", "#col=2")]
+        [InlineData("ftp://example.com/", "ftp", "example.com", "/", "", "")]
+        [InlineData("https://127.0.0.0:80/bar", "https", "127.0.0.0:80", "/bar", "", "")]
+        [InlineData("http://[1080:0:0:0:8:800:200C:417A]/index.html", "http", "[1080:0:0:0:8:800:200C:417A]", "/index.html", "", "")]
+        [InlineData("http://example.com///", "http", "example.com", "///", "", "")]
+        public void FromAbsoluteUriParsingChecks(
+            string uri, 
+            string expectedScheme, 
+            string expectedHost, 
+            string expectedPath, 
+            string expectedQuery, 
+            string expectedFragment)
+        {
+            string scheme = null;
+            var host = new HostString();
+            var path = new PathString();
+            var query = new QueryString();
+            var fragment = new FragmentString();
+            UriHelper.FromAbsolute(uri, out scheme, out host, out path, out query, out fragment);
+
+            Assert.Equal(scheme, expectedScheme);
+            Assert.Equal(host, new HostString(expectedHost));
+            Assert.Equal(path, new PathString(expectedPath));
+            Assert.Equal(query, new QueryString(expectedQuery));
+            Assert.Equal(fragment, new FragmentString(expectedFragment));
+        }
+
+        [Fact]
+        public void FromAbsoluteToBuildAbsolute()
+        {
+            var scheme = "http";
+            var host = new HostString("example.com");
+            var path = new PathString("/index.html");
+            var query = new QueryString("?foo=1");
+            var fragment = new FragmentString("#col=1");
+            var request = UriHelper.BuildAbsolute(scheme, host, path:path, query:query, fragment:fragment);
+
+            string resScheme = null;
+            var resHost = new HostString();
+            var resPath = new PathString();
+            var resQuery = new QueryString();
+            var resFragment = new FragmentString();
+            UriHelper.FromAbsolute(request, out resScheme, out resHost, out resPath, out resQuery, out resFragment);
+
+            Assert.Equal(scheme, resScheme);
+            Assert.Equal(host, resHost);
+            Assert.Equal(path, resPath);
+            Assert.Equal(query, resQuery);
+            Assert.Equal(fragment, resFragment);
+        }
+
+        [Fact]
+        public void BuildAbsoluteNullInputThrowsArgumentNullException()
+        {
+            var resHost = new HostString();
+            var resPath = new PathString();
+            var resQuery = new QueryString();
+            var resFragment = new FragmentString();
+            Assert.Throws<ArgumentNullException>(() => UriHelper.BuildAbsolute(null, resHost,  resPath, resPath,  resQuery,  resFragment));
+
+        }
+
+        [Fact]
+        public void FromAbsoluteNullInputThrowsArgumentNullException()
+        {
+            string resScheme = null;
+            var resHost = new HostString();
+            var resPath = new PathString();
+            var resQuery = new QueryString();
+            var resFragment = new FragmentString();
+            Assert.Throws<ArgumentNullException>(() => UriHelper.FromAbsolute(null, out resScheme, out resHost, out resPath, out resQuery, out resFragment));
+
+        }
+    }
+}
diff --git a/src/Http/Http.Features/src/Authentication/AuthenticateContext.cs b/src/Http/Http.Features/src/Authentication/AuthenticateContext.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e73061667b4a6abd091fa3eac4c86d3305e0c9a4
--- /dev/null
+++ b/src/Http/Http.Features/src/Authentication/AuthenticateContext.cs
@@ -0,0 +1,69 @@
+// 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.Security.Claims;
+
+namespace Microsoft.AspNetCore.Http.Features.Authentication
+{
+    public class AuthenticateContext
+    {
+        public AuthenticateContext(string authenticationScheme)
+        {
+            if (string.IsNullOrEmpty(authenticationScheme))
+            {
+                throw new ArgumentException(nameof(authenticationScheme));
+            }
+
+            AuthenticationScheme = authenticationScheme;
+        }
+
+        public string AuthenticationScheme { get; }
+
+        public bool Accepted { get; private set; }
+
+        public ClaimsPrincipal Principal { get; private set; }
+
+        public IDictionary<string, string> Properties { get; private set; }
+
+        public IDictionary<string, object> Description { get; private set; }
+
+        public Exception Error { get; private set; }
+
+        public virtual void Authenticated(ClaimsPrincipal principal, IDictionary<string, string> properties, IDictionary<string, object> description)
+        {
+            Accepted = true;
+
+            Principal = principal;
+            Properties = properties;
+            Description = description;
+
+            // Set defaults for fields we don't use in case multiple handlers modified the context.
+            Error = null;
+        }
+
+        public virtual void NotAuthenticated()
+        {
+            Accepted = true;
+
+            // Set defaults for fields we don't use in case multiple handlers modified the context.
+            Description = null;
+            Error = null;
+            Principal = null;
+            Properties = null;
+        }
+
+        public virtual void Failed(Exception error)
+        {
+            Accepted = true;
+
+            Error = error;
+
+            // Set defaults for fields we don't use in case multiple handlers modified the context.
+            Description = null;
+            Principal = null;
+            Properties = null;
+        }
+    }
+}
diff --git a/src/Http/Http.Features/src/Authentication/ChallengeBehavior.cs b/src/Http/Http.Features/src/Authentication/ChallengeBehavior.cs
new file mode 100644
index 0000000000000000000000000000000000000000..549d51132a70e2c667c8311da68da87f80b85c17
--- /dev/null
+++ b/src/Http/Http.Features/src/Authentication/ChallengeBehavior.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.Features.Authentication
+{
+    public enum ChallengeBehavior
+    {
+        Automatic,
+        Unauthorized,
+        Forbidden
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/Authentication/ChallengeContext.cs b/src/Http/Http.Features/src/Authentication/ChallengeContext.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c0fe470806aadc076b5672fb96cbeeca6d665424
--- /dev/null
+++ b/src/Http/Http.Features/src/Authentication/ChallengeContext.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.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Http.Features.Authentication
+{
+    public class ChallengeContext
+    {
+        public ChallengeContext(string authenticationScheme)
+            : this(authenticationScheme, properties: null, behavior: ChallengeBehavior.Automatic)
+        {
+        }
+
+        public ChallengeContext(string authenticationScheme, IDictionary<string, string> properties, ChallengeBehavior behavior)
+        {
+            if (string.IsNullOrEmpty(authenticationScheme))
+            {
+                throw new ArgumentException(nameof(authenticationScheme));
+            }
+
+            AuthenticationScheme = authenticationScheme;
+            Properties = properties ?? new Dictionary<string, string>(StringComparer.Ordinal);
+            Behavior = behavior;
+        }
+
+        public string AuthenticationScheme { get; }
+
+        public ChallengeBehavior Behavior { get; }
+
+        public IDictionary<string, string> Properties { get; }
+
+        public bool Accepted { get; private set; }
+
+        public void Accept()
+        {
+            Accepted = true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/Authentication/DescribeSchemesContext.cs b/src/Http/Http.Features/src/Authentication/DescribeSchemesContext.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b25c2c979ac0999d6117c22941a93d06737bb1d9
--- /dev/null
+++ b/src/Http/Http.Features/src/Authentication/DescribeSchemesContext.cs
@@ -0,0 +1,27 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Http.Features.Authentication
+{
+    public class DescribeSchemesContext
+    {
+        private List<IDictionary<string, object>> _results;
+
+        public DescribeSchemesContext()
+        {
+            _results = new List<IDictionary<string, object>>();
+        }
+
+        public IEnumerable<IDictionary<string, object>> Results
+        {
+            get { return _results; }
+        }
+
+        public void Accept(IDictionary<string, object> description)
+        {
+            _results.Add(description);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/Authentication/IAuthenticationHandler.cs b/src/Http/Http.Features/src/Authentication/IAuthenticationHandler.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3b7236418290f7257a9afe56aea87abc0a65f24f
--- /dev/null
+++ b/src/Http/Http.Features/src/Authentication/IAuthenticationHandler.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 System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http.Features.Authentication
+{
+    public interface IAuthenticationHandler
+    {
+        void GetDescriptions(DescribeSchemesContext context);
+
+        Task AuthenticateAsync(AuthenticateContext context);
+
+        Task ChallengeAsync(ChallengeContext context);
+
+        Task SignInAsync(SignInContext context);
+
+        Task SignOutAsync(SignOutContext context);
+    }
+}
diff --git a/src/Http/Http.Features/src/Authentication/IHttpAuthenticationFeature.cs b/src/Http/Http.Features/src/Authentication/IHttpAuthenticationFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..279d6904f08ac9d5ef1ff553bb0053eafa73c823
--- /dev/null
+++ b/src/Http/Http.Features/src/Authentication/IHttpAuthenticationFeature.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Security.Claims;
+
+namespace Microsoft.AspNetCore.Http.Features.Authentication
+{
+    public interface IHttpAuthenticationFeature
+    {
+        ClaimsPrincipal User { get; set; }
+
+        [Obsolete("This is obsolete and will be removed in a future version. See https://go.microsoft.com/fwlink/?linkid=845470.")]
+        IAuthenticationHandler Handler { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/Authentication/SignInContext.cs b/src/Http/Http.Features/src/Authentication/SignInContext.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f04dade51b994d77faf311ce4c88dde2030e8e82
--- /dev/null
+++ b/src/Http/Http.Features/src/Authentication/SignInContext.cs
@@ -0,0 +1,42 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Security.Claims;
+
+namespace Microsoft.AspNetCore.Http.Features.Authentication
+{
+    public class SignInContext
+    {
+        public SignInContext(string authenticationScheme, ClaimsPrincipal principal, IDictionary<string, string> properties)
+        {
+            if (string.IsNullOrEmpty(authenticationScheme))
+            {
+                throw new ArgumentException(nameof(authenticationScheme));
+            }
+
+            if (principal == null)
+            {
+                throw new ArgumentNullException(nameof(principal));
+            }
+
+            AuthenticationScheme = authenticationScheme;
+            Principal = principal;
+            Properties = properties ?? new Dictionary<string, string>(StringComparer.Ordinal);
+        }
+
+        public string AuthenticationScheme { get; }
+
+        public ClaimsPrincipal Principal { get; }
+
+        public IDictionary<string, string> Properties { get; }
+
+        public bool Accepted { get; private set; }
+
+        public void Accept()
+        {
+            Accepted = true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/Authentication/SignOutContext.cs b/src/Http/Http.Features/src/Authentication/SignOutContext.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c752f057dfe01cab2222e16b0c55166cfaacd226
--- /dev/null
+++ b/src/Http/Http.Features/src/Authentication/SignOutContext.cs
@@ -0,0 +1,33 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Http.Features.Authentication
+{
+    public class SignOutContext
+    {
+        public SignOutContext(string authenticationScheme, IDictionary<string, string> properties)
+        {
+            if (string.IsNullOrEmpty(authenticationScheme))
+            {
+                throw new ArgumentException(nameof(authenticationScheme));
+            }
+
+            AuthenticationScheme = authenticationScheme;
+            Properties = properties ?? new Dictionary<string, string>(StringComparer.Ordinal);
+        }
+
+        public string AuthenticationScheme { get; }
+
+        public IDictionary<string, string> Properties { get; }
+
+        public bool Accepted { get; private set; }
+
+        public void Accept()
+        {
+            Accepted = true;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/CookieOptions.cs b/src/Http/Http.Features/src/CookieOptions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..27141a32f2864a5e35c27ecd90a630796e4c594b
--- /dev/null
+++ b/src/Http/Http.Features/src/CookieOptions.cs
@@ -0,0 +1,69 @@
+// 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
+{
+    /// <summary>
+    /// Options used to create a new cookie.
+    /// </summary>
+    public class CookieOptions
+    {
+        /// <summary>
+        /// Creates a default cookie with a path of '/'.
+        /// </summary>
+        public CookieOptions()
+        {
+            Path = "/";
+        }
+
+        /// <summary>
+        /// Gets or sets the domain to associate the cookie with.
+        /// </summary>
+        /// <returns>The domain to associate the cookie with.</returns>
+        public string Domain { get; set; }
+
+        /// <summary>
+        /// Gets or sets the cookie path.
+        /// </summary>
+        /// <returns>The cookie path.</returns>
+        public string Path { get; set; }
+
+        /// <summary>
+        /// Gets or sets the expiration date and time for the cookie.
+        /// </summary>
+        /// <returns>The expiration date and time for the cookie.</returns>
+        public DateTimeOffset? Expires { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value that indicates whether to transmit the cookie using Secure Sockets Layer (SSL)--that is, over HTTPS only.
+        /// </summary>
+        /// <returns>true to transmit the cookie only over an SSL connection (HTTPS); otherwise, false.</returns>
+        public bool Secure { get; set; }
+
+        /// <summary>
+        /// Gets or sets the value for the SameSite attribute of the cookie. The default value is <see cref="SameSiteMode.Lax"/>
+        /// </summary>
+        /// <returns>The <see cref="SameSiteMode"/> representing the enforcement mode of the cookie.</returns>
+        public SameSiteMode SameSite { get; set; } = SameSiteMode.Lax;
+
+        /// <summary>
+        /// Gets or sets a value that indicates whether a cookie is accessible by client-side script.
+        /// </summary>
+        /// <returns>true if a cookie must not be accessible by client-side script; otherwise, false.</returns>
+        public bool HttpOnly { get; set; }
+
+        /// <summary>
+        /// Gets or sets the max-age for the cookie.
+        /// </summary>
+        /// <returns>The max-age date and time for the cookie.</returns>
+        public TimeSpan? MaxAge { get; set; }
+
+        /// <summary>
+        /// Indicates if this cookie is essential for the application to function correctly. If true then
+        /// consent policy checks may be bypassed. The default value is false.
+        /// </summary>
+        public bool IsEssential { get; set; }
+    }
+}
diff --git a/src/Http/Http.Features/src/FeatureCollection.cs b/src/Http/Http.Features/src/FeatureCollection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e79ecfee224aca99d3186735357888dd9560651b
--- /dev/null
+++ b/src/Http/Http.Features/src/FeatureCollection.cs
@@ -0,0 +1,119 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class FeatureCollection : IFeatureCollection
+    {
+        private static KeyComparer FeatureKeyComparer = new KeyComparer();
+        private readonly IFeatureCollection _defaults;
+        private IDictionary<Type, object> _features;
+        private volatile int _containerRevision;
+
+        public FeatureCollection()
+        {
+        }
+
+        public FeatureCollection(IFeatureCollection defaults)
+        {
+            _defaults = defaults;
+        }
+
+        public virtual int Revision
+        {
+            get { return _containerRevision + (_defaults?.Revision ?? 0); }
+        }
+
+        public bool IsReadOnly { get { return false; } }
+
+        public object this[Type key]
+        {
+            get
+            {
+                if (key == null)
+                {
+                    throw new ArgumentNullException(nameof(key));
+                }
+
+                object result;
+                return _features != null && _features.TryGetValue(key, out result) ? result : _defaults?[key];
+            }
+            set
+            {
+                if (key == null)
+                {
+                    throw new ArgumentNullException(nameof(key));
+                }
+
+                if (value == null)
+                {
+                    if (_features != null && _features.Remove(key))
+                    {
+                        _containerRevision++;
+                    }
+                    return;
+                }
+
+                if (_features == null)
+                {
+                    _features = new Dictionary<Type, object>();
+                }
+                _features[key] = value;
+                _containerRevision++;
+            }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        public IEnumerator<KeyValuePair<Type, object>> GetEnumerator()
+        {
+            if (_features != null)
+            {
+                foreach (var pair in _features)
+                {
+                    yield return pair;
+                }
+            }
+
+            if (_defaults != null)
+            {
+                // Don't return features masked by the wrapper.
+                foreach (var pair in _features == null ? _defaults : _defaults.Except(_features, FeatureKeyComparer))
+                {
+                    yield return pair;
+                }
+            }
+        }
+
+        public TFeature Get<TFeature>()
+        {
+            return (TFeature)this[typeof(TFeature)];
+        }
+
+        public void Set<TFeature>(TFeature instance)
+        {
+            this[typeof(TFeature)] = instance;
+        }
+
+        private class KeyComparer : IEqualityComparer<KeyValuePair<Type, object>>
+        {
+            public bool Equals(KeyValuePair<Type, object> x, KeyValuePair<Type, object> y)
+            {
+                return x.Key.Equals(y.Key);
+            }
+
+            public int GetHashCode(KeyValuePair<Type, object> obj)
+            {
+                return obj.Key.GetHashCode();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/FeatureReference.cs b/src/Http/Http.Features/src/FeatureReference.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5016602123106e2fa73e7e8cf2a5af176990ebbd
--- /dev/null
+++ b/src/Http/Http.Features/src/FeatureReference.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.
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public struct FeatureReference<T>
+    {
+        private T _feature;
+        private int _revision;
+
+        private FeatureReference(T feature, int revision)
+        {
+            _feature = feature;
+            _revision = revision;
+        }
+
+        public static readonly FeatureReference<T> Default = new FeatureReference<T>(default(T), -1);
+
+        public T Fetch(IFeatureCollection features)
+        {
+            if (_revision == features.Revision)
+            {
+                return _feature;
+            }
+            _feature = (T)features[typeof(T)];
+            _revision = features.Revision;
+            return _feature;
+        }
+
+        public T Update(IFeatureCollection features, T feature)
+        {
+            features[typeof(T)] = feature;
+            _feature = feature;
+            _revision = features.Revision;
+            return feature;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/FeatureReferences.cs b/src/Http/Http.Features/src/FeatureReferences.cs
new file mode 100644
index 0000000000000000000000000000000000000000..38bd2ec27aa6f57bc5370935863213b034a6e1b4
--- /dev/null
+++ b/src/Http/Http.Features/src/FeatureReferences.cs
@@ -0,0 +1,98 @@
+// 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.Runtime.CompilerServices;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public struct FeatureReferences<TCache>
+    {
+        public FeatureReferences(IFeatureCollection collection)
+        {
+            Collection = collection;
+            Cache = default(TCache);
+            Revision = collection.Revision;
+        }
+
+        public IFeatureCollection Collection { get; private set; }
+        public int Revision { get; private set; }
+
+        // cache is a public field because the code calling Fetch must
+        // be able to pass ref values that "dot through" the TCache struct memory, 
+        // if it was a Property then that getter would return a copy of the memory
+        // preventing the use of "ref"
+        public TCache Cache;
+
+        // Careful with modifications to the Fetch method; it is carefully constructed for inlining
+        // See: https://github.com/aspnet/HttpAbstractions/pull/704
+        // This method is 59 IL bytes and at inline call depth 3 from accessing a property.
+        // This combination is enough for the jit to consider it an "unprofitable inline"
+        // Aggressively inlining it causes the entire call chain to dissolve:
+        //
+        // This means this call graph:
+        //
+        // HttpResponse.Headers -> Response.HttpResponseFeature -> Fetch -> Fetch      -> Revision
+        //                                                               -> Collection -> Collection
+        //                                                                             -> Collection.Revision
+        // Has 6 calls eliminated and becomes just:                                    -> UpdateCached
+        //
+        // HttpResponse.Headers -> Collection.Revision
+        //                      -> UpdateCached (not called on fast path)
+        //
+        // As this is inlined at the callsite we want to keep the method small, so it only detects
+        // if a reset or update is required and all the reset and update logic is pushed to UpdateCached.
+        //
+        // Generally Fetch is called at a ratio > x4 of UpdateCached so this is a large gain
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public TFeature Fetch<TFeature, TState>(
+            ref TFeature cached,
+            TState state,
+            Func<TState, TFeature> factory) where TFeature : class
+        {
+            var flush = false;
+            var revision = Collection.Revision;
+            if (Revision != revision)
+            {
+                // Clear cached value to force call to UpdateCached
+                cached = null;
+                // Collection changed, clear whole feature cache
+                flush = true;
+            }
+
+            return cached ?? UpdateCached(ref cached, state, factory, revision, flush);
+        }
+
+        // Update and cache clearing logic, when the fast-path in Fetch isn't applicable
+        private TFeature UpdateCached<TFeature, TState>(ref TFeature cached, TState state, Func<TState, TFeature> factory, int revision, bool flush) where TFeature : class
+        {
+            if (flush)
+            {
+                // Collection detected as changed, clear cache
+                Cache = default(TCache);
+            }
+
+            cached = Collection.Get<TFeature>();
+            if (cached == null)
+            {
+                // Item not in collection, create it with factory
+                cached = factory(state);
+                // Add item to IFeatureCollection
+                Collection.Set(cached);
+                // Revision changed by .Set, update revision to new value
+                Revision = Collection.Revision;
+            }
+            else if (flush)
+            {
+                // Cache was cleared, but item retrived from current Collection for version
+                // so use passed in revision rather than making another virtual call
+                Revision = revision;
+            }
+
+            return cached;
+        }
+
+        public TFeature Fetch<TFeature>(ref TFeature cached, Func<IFeatureCollection, TFeature> factory)
+            where TFeature : class => Fetch(ref cached, Collection, factory);
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/IFeatureCollection.cs b/src/Http/Http.Features/src/IFeatureCollection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f7b23ed16f25d98f7e157ded3672e09bf6272277
--- /dev/null
+++ b/src/Http/Http.Features/src/IFeatureCollection.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 System;
+using System.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    /// <summary>
+    /// Represents a collection of HTTP features.
+    /// </summary>
+    public interface IFeatureCollection : IEnumerable<KeyValuePair<Type, object>>
+    {
+        /// <summary>
+        /// Indicates if the collection can be modified.
+        /// </summary>
+        bool IsReadOnly { get; }
+
+        /// <summary>
+        /// Incremented for each modification and can be used to verify cached results.
+        /// </summary>
+        int Revision { get; }
+
+        /// <summary>
+        /// Gets or sets a given feature. Setting a null value removes the feature.
+        /// </summary>
+        /// <param name="key"></param>
+        /// <returns>The requested feature, or null if it is not present.</returns>
+        object this[Type key] { get; set; }
+
+        /// <summary>
+        /// Retrieves the requested feature from the collection.
+        /// </summary>
+        /// <typeparam name="TFeature">The feature key.</typeparam>
+        /// <returns>The requested feature, or null if it is not present.</returns>
+        TFeature Get<TFeature>();
+
+        /// <summary>
+        /// Sets the given feature in the collection.
+        /// </summary>
+        /// <typeparam name="TFeature">The feature key.</typeparam>
+        /// <param name="instance">The feature value.</param>
+        void Set<TFeature>(TFeature instance);
+    }
+}
diff --git a/src/Http/Http.Features/src/IFormCollection.cs b/src/Http/Http.Features/src/IFormCollection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..237d311ae801c600a1933573528a86fa9a1c8b63
--- /dev/null
+++ b/src/Http/Http.Features/src/IFormCollection.cs
@@ -0,0 +1,94 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Represents the parsed form values sent with the HttpRequest.
+    /// </summary>
+    public interface IFormCollection : IEnumerable<KeyValuePair<string, StringValues>>
+    {
+        /// <summary>
+        ///     Gets the number of elements contained in the <see cref="IFormCollection" />.
+        /// </summary>
+        /// <returns>
+        ///     The number of elements contained in the <see cref="IFormCollection" />.
+        /// </returns>
+        int Count { get; }
+
+        /// <summary>
+        ///     Gets an <see cref="ICollection{T}" /> containing the keys of the
+        ///     <see cref="IFormCollection" />.
+        /// </summary>
+        /// <returns>
+        ///     An <see cref="ICollection{T}" /> containing the keys of the object
+        ///     that implements <see cref="IFormCollection" />.
+        /// </returns>
+        ICollection<string> Keys { get; }
+
+        /// <summary>
+        ///     Determines whether the <see cref="IFormCollection" /> contains an element
+        ///     with the specified key.
+        /// </summary>
+        /// <param name="key">
+        /// The key to locate in the <see cref="IFormCollection" />.
+        /// </param>
+        /// <returns>
+        ///     true if the <see cref="IFormCollection" /> contains an element with
+        ///     the key; otherwise, false.
+        /// </returns>
+        /// <exception cref="System.ArgumentNullException">
+        ///     key is null.
+        /// </exception>
+        bool ContainsKey(string key);
+
+        /// <summary>
+        ///    Gets the value associated with the specified key.
+        /// </summary>
+        /// <param name="key">
+        ///     The key of the value to get.
+        /// </param>
+        /// <param name="value">
+        ///     The key of the value to get.
+        ///     When this method returns, the value associated with the specified key, if the
+        ///     key is found; otherwise, the default value for the type of the value parameter.
+        ///     This parameter is passed uninitialized.
+        /// </param>
+        /// <returns>
+        ///    true if the object that implements <see cref="IFormCollection" /> contains
+        ///     an element with the specified key; otherwise, false.
+        /// </returns>
+        /// <exception cref="System.ArgumentNullException">
+        ///     key is null.
+        /// </exception>
+        bool TryGetValue(string key, out StringValues value);
+
+        /// <summary>
+        ///     Gets the value with the specified key.
+        /// </summary>
+        /// <param name="key">
+        ///     The key of the value to get.
+        /// </param>
+        /// <returns>
+        ///     The element with the specified key, or <c>StringValues.Empty</c> if the key is not present.
+        /// </returns>
+        /// <exception cref="System.ArgumentNullException">
+        ///     key is null.
+        /// </exception>
+        /// <remarks>
+        ///     <see cref="IFormCollection" /> has a different indexer contract than
+        ///     <see cref="IDictionary{TKey, TValue}" />, as it will return <c>StringValues.Empty</c> for missing entries
+        ///     rather than throwing an Exception.
+        /// </remarks>
+        StringValues this[string key] { get; }
+
+        /// <summary>
+        /// The file collection sent with the request.
+        /// </summary>
+        /// <returns>The files included with the request.</returns>
+        IFormFileCollection Files { get; }
+    }
+}
diff --git a/src/Http/Http.Features/src/IFormFeature.cs b/src/Http/Http.Features/src/IFormFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f10ed47b806ef7f00408c7aa13a9336d9b673789
--- /dev/null
+++ b/src/Http/Http.Features/src/IFormFeature.cs
@@ -0,0 +1,34 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public interface IFormFeature
+    {
+        /// <summary>
+        /// Indicates if the request has a supported form content-type.
+        /// </summary>
+        bool HasFormContentType { get; }
+
+        /// <summary>
+        /// The parsed form, if any.
+        /// </summary>
+        IFormCollection Form { get; set; }
+
+        /// <summary>
+        /// Parses the request body as a form.
+        /// </summary>
+        /// <returns></returns>
+        IFormCollection ReadForm();
+
+        /// <summary>
+        /// Parses the request body as a form.
+        /// </summary>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken);
+    }
+}
diff --git a/src/Http/Http.Features/src/IFormFile.cs b/src/Http/Http.Features/src/IFormFile.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f52e71bfee16c24e14182f05d6c3a0f2b05f64cb
--- /dev/null
+++ b/src/Http/Http.Features/src/IFormFile.cs
@@ -0,0 +1,63 @@
+// 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;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Represents a file sent with the HttpRequest.
+    /// </summary>
+    public interface IFormFile
+    {
+        /// <summary>
+        /// Gets the raw Content-Type header of the uploaded file.
+        /// </summary>
+        string ContentType { get; }
+
+        /// <summary>
+        /// Gets the raw Content-Disposition header of the uploaded file.
+        /// </summary>
+        string ContentDisposition { get; }
+
+        /// <summary>
+        /// Gets the header dictionary of the uploaded file.
+        /// </summary>
+        IHeaderDictionary Headers { get; }
+
+        /// <summary>
+        /// Gets the file length in bytes.
+        /// </summary>
+        long Length { get; }
+
+        /// <summary>
+        /// Gets the form field name from the Content-Disposition header.
+        /// </summary>
+        string Name { get; }
+
+        /// <summary>
+        /// Gets the file name from the Content-Disposition header.
+        /// </summary>
+        string FileName { get; }
+
+        /// <summary>
+        /// Opens the request stream for reading the uploaded file.
+        /// </summary>
+        Stream OpenReadStream();
+
+        /// <summary>
+        /// Copies the contents of the uploaded file to the <paramref name="target"/> stream.
+        /// </summary>
+        /// <param name="target">The stream to copy the file contents to.</param>
+        void CopyTo(Stream target);
+
+        /// <summary>
+        /// Asynchronously copies the contents of the uploaded file to the <paramref name="target"/> stream.
+        /// </summary>
+        /// <param name="target">The stream to copy the file contents to.</param>
+        /// <param name="cancellationToken"></param>
+        Task CopyToAsync(Stream target, CancellationToken cancellationToken = default(CancellationToken));
+    }
+}
diff --git a/src/Http/Http.Features/src/IFormFileCollection.cs b/src/Http/Http.Features/src/IFormFileCollection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e66c96e05ddcd777c43422fa7a74ee700f9fdcb1
--- /dev/null
+++ b/src/Http/Http.Features/src/IFormFileCollection.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.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Represents the collection of files sent with the HttpRequest.
+    /// </summary>
+    public interface IFormFileCollection : IReadOnlyList<IFormFile>
+    {
+        IFormFile this[string name] { get; }
+
+        IFormFile GetFile(string name);
+
+        IReadOnlyList<IFormFile> GetFiles(string name);
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/IHeaderDictionary.cs b/src/Http/Http.Features/src/IHeaderDictionary.cs
new file mode 100644
index 0000000000000000000000000000000000000000..dfde3f33e31e1de2af23f0d0b8d8606373fa7126
--- /dev/null
+++ b/src/Http/Http.Features/src/IHeaderDictionary.cs
@@ -0,0 +1,26 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Represents HttpRequest and HttpResponse headers
+    /// </summary>
+    public interface IHeaderDictionary : IDictionary<string, StringValues>
+    {
+        /// <summary>
+        /// IHeaderDictionary has a different indexer contract than IDictionary, where it will return StringValues.Empty for missing entries.
+        /// </summary>
+        /// <param name="key"></param>
+        /// <returns>The stored value, or StringValues.Empty if the key is not present.</returns>
+        new StringValues this[string key] { get; set; }
+
+        /// <summary>
+        /// Strongly typed access to the Content-Length header. Implementations must keep this in sync with the string representation.
+        /// </summary>
+        long? ContentLength { get; set; }
+    }
+}
diff --git a/src/Http/Http.Features/src/IHttpBodyControlFeature.cs b/src/Http/Http.Features/src/IHttpBodyControlFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3f61be97882ca897897eab25632527f5cfd5e268
--- /dev/null
+++ b/src/Http/Http.Features/src/IHttpBodyControlFeature.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    /// <summary>
+    /// Controls the IO behavior for the <see cref="IHttpRequestFeature.Body"/> and <see cref="IHttpResponseFeature.Body"/> 
+    /// </summary>
+    public interface IHttpBodyControlFeature
+    {
+        /// <summary>
+        /// Gets or sets a value that controls whether synchronous IO is allowed for the <see cref="IHttpRequestFeature.Body"/> and <see cref="IHttpResponseFeature.Body"/> 
+        /// </summary>
+        bool AllowSynchronousIO { get; set; }
+    }
+}
diff --git a/src/Http/Http.Features/src/IHttpBufferingFeature.cs b/src/Http/Http.Features/src/IHttpBufferingFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..fae7f3d0ffd3e7bbd4d550447511d4c9241ebf68
--- /dev/null
+++ b/src/Http/Http.Features/src/IHttpBufferingFeature.cs
@@ -0,0 +1,11 @@
+// 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.Features
+{
+    public interface IHttpBufferingFeature
+    {
+        void DisableRequestBuffering();
+        void DisableResponseBuffering();
+    }
+}
diff --git a/src/Http/Http.Features/src/IHttpConnectionFeature.cs b/src/Http/Http.Features/src/IHttpConnectionFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..932e9bfe2c1c408c9ab91ae37960ddd3816d76c6
--- /dev/null
+++ b/src/Http/Http.Features/src/IHttpConnectionFeature.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.Net;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    /// <summary>
+    /// Information regarding the TCP/IP connection carrying the request.
+    /// </summary>
+    public interface IHttpConnectionFeature
+    {
+        /// <summary>
+        /// The unique identifier for the connection the request was received on. This is primarily for diagnostic purposes.
+        /// </summary>
+        string ConnectionId { get; set; }
+
+        /// <summary>
+        /// The IPAddress of the client making the request. Note this may be for a proxy rather than the end user.
+        /// </summary>
+        IPAddress RemoteIpAddress { get; set; }
+
+        /// <summary>
+        /// The local IPAddress on which the request was received.
+        /// </summary>
+        IPAddress LocalIpAddress { get; set; }
+
+        /// <summary>
+        /// The remote port of the client making the request.
+        /// </summary>
+        int RemotePort { get; set; }
+
+        /// <summary>
+        /// The local port on which the request was received.
+        /// </summary>
+        int LocalPort { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/IHttpMaxRequestBodySizeFeature.cs b/src/Http/Http.Features/src/IHttpMaxRequestBodySizeFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c02000c72afa3809a784b8d6a64982702d2dd136
--- /dev/null
+++ b/src/Http/Http.Features/src/IHttpMaxRequestBodySizeFeature.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.
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    /// <summary>
+    /// Feature to inspect and modify the maximum request body size for a single request.
+    /// </summary>
+    public interface IHttpMaxRequestBodySizeFeature
+    {
+        /// <summary>
+        /// Indicates whether <see cref="MaxRequestBodySize"/> is read-only.
+        /// If true, this could mean that the request body has already been read from
+        /// or that <see cref="IHttpUpgradeFeature.UpgradeAsync"/> was called.
+        /// </summary>
+        bool IsReadOnly { get; }
+
+        /// <summary>
+        /// The maximum allowed size of the current request body in bytes.
+        /// When set to null, the maximum request body size is unlimited.
+        /// This cannot be modified after the reading the request body has started.
+        /// This limit does not affect upgraded connections which are always unlimited.
+        /// </summary>
+        /// <remarks>
+        /// Defaults to the server's global max request body size limit.
+        /// </remarks>
+        long? MaxRequestBodySize { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/IHttpRequestFeature.cs b/src/Http/Http.Features/src/IHttpRequestFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5a84221b5756f271a15095a07fd511994c90e734
--- /dev/null
+++ b/src/Http/Http.Features/src/IHttpRequestFeature.cs
@@ -0,0 +1,77 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    /// <summary>
+    /// Contains the details of a given request. These properties should all be mutable.
+    /// None of these properties should ever be set to null.
+    /// </summary>
+    public interface IHttpRequestFeature
+    {
+        /// <summary>
+        /// The HTTP-version as defined in RFC 7230. E.g. "HTTP/1.1"
+        /// </summary>
+        string Protocol { get; set; }
+
+        /// <summary>
+        /// The request uri scheme. E.g. "http" or "https". Note this value is not included
+        /// in the original request, it is inferred by checking if the transport used a TLS
+        /// connection or not.
+        /// </summary>
+        string Scheme { get; set; }
+
+        /// <summary>
+        /// The request method as defined in RFC 7230. E.g. "GET", "HEAD", "POST", etc..
+        /// </summary>
+        string Method { get; set; }
+
+        /// <summary>
+        /// The first portion of the request path associated with application root. The value
+        /// is un-escaped. The value may be string.Empty.
+        /// </summary>
+        string PathBase { get; set; }
+
+        /// <summary>
+        /// The portion of the request path that identifies the requested resource. The value
+        /// is un-escaped. The value may be string.Empty if <see cref="PathBase"/> contains the
+        /// full path.
+        /// </summary>
+        string Path { get; set; }
+
+        /// <summary>
+        /// The query portion of the request-target as defined in RFC 7230. The value
+        /// may be string.Empty. If not empty then the leading '?' will be included. The value
+        /// is in its original form, without un-escaping.
+        /// </summary>
+        string QueryString { get; set; }
+
+        /// <summary>
+        /// The request target as it was sent in the HTTP request. This property contains the
+        /// raw path and full query, as well as other request targets such as * for OPTIONS
+        /// requests (https://tools.ietf.org/html/rfc7230#section-5.3).
+        /// </summary>
+        /// <remarks>
+        /// This property is not used internally for routing or authorization decisions. It has not
+        /// been UrlDecoded and care should be taken in its use.
+        /// </remarks>
+        string RawTarget { get; set; }
+
+        /// <summary>
+        /// Headers included in the request, aggregated by header name. The values are not split
+        /// or merged across header lines. E.g. The following headers:
+        /// HeaderA: value1, value2
+        /// HeaderA: value3
+        /// Result in Headers["HeaderA"] = { "value1, value2", "value3" }
+        /// </summary>
+        IHeaderDictionary Headers { get; set; }
+
+        /// <summary>
+        /// A <see cref="Stream"/> representing the request body, if any. Stream.Null may be used
+        /// to represent an empty request body.
+        /// </summary>
+        Stream Body { get; set; }
+    }
+}
diff --git a/src/Http/Http.Features/src/IHttpRequestIdentifierFeature.cs b/src/Http/Http.Features/src/IHttpRequestIdentifierFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9b0b5201d7df1e184f5ecc025b90a86f99f7d146
--- /dev/null
+++ b/src/Http/Http.Features/src/IHttpRequestIdentifierFeature.cs
@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    /// <summary>
+    /// Feature to identify a request.
+    /// </summary>
+    public interface IHttpRequestIdentifierFeature
+    {
+        /// <summary>
+        /// Identifier to trace a request.
+        /// </summary>
+        string TraceIdentifier { get; set; }
+    }
+}
diff --git a/src/Http/Http.Features/src/IHttpRequestLifetimeFeature.cs b/src/Http/Http.Features/src/IHttpRequestLifetimeFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1bdac15766cc8246453d0740769c1106c0315141
--- /dev/null
+++ b/src/Http/Http.Features/src/IHttpRequestLifetimeFeature.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public interface IHttpRequestLifetimeFeature
+    {
+        /// <summary>
+        /// A <see cref="CancellationToken"/> that fires if the request is aborted and
+        /// the application should cease processing. The token will not fire if the request
+        /// completes successfully.
+        /// </summary>
+        CancellationToken RequestAborted { get; set; }
+
+        /// <summary>
+        /// Forcefully aborts the request if it has not already completed. This will result in
+        /// RequestAborted being triggered.
+        /// </summary>
+        void Abort();
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/IHttpResponseFeature.cs b/src/Http/Http.Features/src/IHttpResponseFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9d3b957efb8443ab25cc0dac023502991344cb5b
--- /dev/null
+++ b/src/Http/Http.Features/src/IHttpResponseFeature.cs
@@ -0,0 +1,59 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    /// <summary>
+    /// Represents the fields and state of an HTTP response.
+    /// </summary>
+    public interface IHttpResponseFeature
+    {
+        /// <summary>
+        /// The status-code as defined in RFC 7230. The default value is 200.
+        /// </summary>
+        int StatusCode { get; set; }
+
+        /// <summary>
+        /// The reason-phrase as defined in RFC 7230. Note this field is no longer supported by HTTP/2.
+        /// </summary>
+        string ReasonPhrase { get; set; }
+
+        /// <summary>
+        /// The response headers to send. Headers with multiple values will be emitted as multiple headers.
+        /// </summary>
+        IHeaderDictionary Headers { get; set; }
+
+        /// <summary>
+        /// The <see cref="Stream"/> for writing the response body.
+        /// </summary>
+        Stream Body { get; set; }
+
+        /// <summary>
+        /// Indicates if the response has started. If true, the <see cref="StatusCode"/>,
+        /// <see cref="ReasonPhrase"/>, and <see cref="Headers"/> are now immutable, and
+        /// OnStarting should no longer be called.
+        /// </summary>
+        bool HasStarted { get; }
+
+        /// <summary>
+        /// Registers a callback to be invoked just before the response starts. This is the
+        /// last chance to modify the <see cref="Headers"/>, <see cref="StatusCode"/>, or
+        /// <see cref="ReasonPhrase"/>.
+        /// </summary>
+        /// <param name="callback">The callback to invoke when starting the response.</param>
+        /// <param name="state">The state to pass into the callback.</param>
+        void OnStarting(Func<object, Task> callback, object state);
+
+        /// <summary>
+        /// Registers a callback to be invoked after a response has fully completed. This is
+        /// intended for resource cleanup.
+        /// </summary>
+        /// <param name="callback">The callback to invoke after the response has completed.</param>
+        /// <param name="state">The state to pass into the callback.</param>
+        void OnCompleted(Func<object, Task> callback, object state);
+    }
+}
diff --git a/src/Http/Http.Features/src/IHttpSendFileFeature.cs b/src/Http/Http.Features/src/IHttpSendFileFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1e2684130fa1a25ae4e311b0b77471a4dee661d1
--- /dev/null
+++ b/src/Http/Http.Features/src/IHttpSendFileFeature.cs
@@ -0,0 +1,26 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    /// <summary>
+    /// Provides an efficient mechanism for transferring files from disk to the network.
+    /// </summary>
+    public interface IHttpSendFileFeature
+    {
+        /// <summary>
+        /// Sends the requested file in the response body. This may bypass the IHttpResponseFeature.Body
+        /// <see cref="Stream"/>. A response may include multiple writes.
+        /// </summary>
+        /// <param name="path">The full disk path to the file.</param>
+        /// <param name="offset">The offset in the file to start at.</param>
+        /// <param name="count">The number of bytes to send, or null to send the remainder of the file.</param>
+        /// <param name="cancellation">A <see cref="CancellationToken"/> used to abort the transmission.</param>
+        /// <returns></returns>
+        Task SendFileAsync(string path, long offset, long? count, CancellationToken cancellation);
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/IHttpUpgradeFeature.cs b/src/Http/Http.Features/src/IHttpUpgradeFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e434fe0b97c61f286c45cccb484f77d640fc1744
--- /dev/null
+++ b/src/Http/Http.Features/src/IHttpUpgradeFeature.cs
@@ -0,0 +1,24 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public interface IHttpUpgradeFeature
+    {
+        /// <summary>
+        /// Indicates if the server can upgrade this request to an opaque, bidirectional stream.
+        /// </summary>
+        bool IsUpgradableRequest { get; }
+
+        /// <summary>
+        /// Attempt to upgrade the request to an opaque, bidirectional stream. The response status code
+        /// and headers need to be set before this is invoked. Check <see cref="IsUpgradableRequest"/>
+        /// before invoking.
+        /// </summary>
+        /// <returns></returns>
+        Task<Stream> UpgradeAsync();
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/IHttpWebSocketFeature.cs b/src/Http/Http.Features/src/IHttpWebSocketFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c1d116126a5f194cfcfa0cfaebb7b1bc8b6c776a
--- /dev/null
+++ b/src/Http/Http.Features/src/IHttpWebSocketFeature.cs
@@ -0,0 +1,24 @@
+// 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.Net.WebSockets;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public interface IHttpWebSocketFeature
+    {
+        /// <summary>
+        /// Indicates if this is a WebSocket upgrade request.
+        /// </summary>
+        bool IsWebSocketRequest { get; }
+
+        /// <summary>
+        /// Attempts to upgrade the request to a <see cref="WebSocket"/>. Check <see cref="IsWebSocketRequest"/>
+        /// before invoking this.
+        /// </summary>
+        /// <param name="context"></param>
+        /// <returns></returns>
+        Task<WebSocket> AcceptAsync(WebSocketAcceptContext context);
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/IItemsFeature.cs b/src/Http/Http.Features/src/IItemsFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..bea03e466c1b7be18622e41727e13ec77d699c28
--- /dev/null
+++ b/src/Http/Http.Features/src/IItemsFeature.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.
+
+using System.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public interface IItemsFeature
+    {
+        IDictionary<object, object> Items { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/IQueryCollection.cs b/src/Http/Http.Features/src/IQueryCollection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5d45ad2493604e697c6cc84083c014cbda49c8a8
--- /dev/null
+++ b/src/Http/Http.Features/src/IQueryCollection.cs
@@ -0,0 +1,88 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    ///     Represents the HttpRequest query string collection
+    /// </summary>
+    public interface IQueryCollection : IEnumerable<KeyValuePair<string, StringValues>>
+    {
+        /// <summary>
+        ///     Gets the number of elements contained in the <see cref="IQueryCollection" />.
+        /// </summary>
+        /// <returns>
+        ///     The number of elements contained in the <see cref="IQueryCollection" />.
+        /// </returns>
+        int Count { get; }
+
+        /// <summary>
+        ///     Gets an <see cref="ICollection{T}" /> containing the keys of the
+        ///     <see cref="IQueryCollection" />.
+        /// </summary>
+        /// <returns>
+        ///     An <see cref="ICollection{T}" /> containing the keys of the object
+        ///     that implements <see cref="IQueryCollection" />.
+        /// </returns>
+        ICollection<string> Keys { get; }
+
+        /// <summary>
+        ///     Determines whether the <see cref="IQueryCollection" /> contains an element
+        ///     with the specified key.
+        /// </summary>
+        /// <param name="key">
+        /// The key to locate in the <see cref="IQueryCollection" />.
+        /// </param>
+        /// <returns>
+        ///     true if the <see cref="IQueryCollection" /> contains an element with
+        ///     the key; otherwise, false.
+        /// </returns>
+        /// <exception cref="System.ArgumentNullException">
+        ///     key is null.
+        /// </exception>
+        bool ContainsKey(string key);
+
+        /// <summary>
+        ///    Gets the value associated with the specified key.
+        /// </summary>
+        /// <param name="key">
+        ///     The key of the value to get.
+        /// </param>
+        /// <param name="value">
+        ///     The key of the value to get.
+        ///     When this method returns, the value associated with the specified key, if the
+        ///     key is found; otherwise, the default value for the type of the value parameter.
+        ///     This parameter is passed uninitialized.
+        /// </param>
+        /// <returns>
+        ///    true if the object that implements <see cref="IQueryCollection" /> contains
+        ///     an element with the specified key; otherwise, false.
+        /// </returns>
+        /// <exception cref="System.ArgumentNullException">
+        ///     key is null.
+        /// </exception>
+        bool TryGetValue(string key, out StringValues value);
+
+        /// <summary>
+        ///     Gets the value with the specified key.
+        /// </summary>
+        /// <param name="key">
+        ///     The key of the value to get.
+        /// </param>
+        /// <returns>
+        ///     The element with the specified key, or <c>StringValues.Empty</c> if the key is not present.
+        /// </returns>
+        /// <exception cref="System.ArgumentNullException">
+        ///     key is null.
+        /// </exception>
+        /// <remarks>
+        ///     <see cref="IQueryCollection" /> has a different indexer contract than
+        ///     <see cref="IDictionary{TKey, TValue}" />, as it will return <c>StringValues.Empty</c> for missing entries
+        ///     rather than throwing an Exception.
+        /// </remarks>
+        StringValues this[string key] { get; }
+    }
+}
diff --git a/src/Http/Http.Features/src/IQueryFeature.cs b/src/Http/Http.Features/src/IQueryFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4f307f8f9017bb77db1ecfa84ed723743f6c2153
--- /dev/null
+++ b/src/Http/Http.Features/src/IQueryFeature.cs
@@ -0,0 +1,10 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public interface IQueryFeature
+    {
+        IQueryCollection Query { get; set; }
+    }
+}
diff --git a/src/Http/Http.Features/src/IRequestCookieCollection.cs b/src/Http/Http.Features/src/IRequestCookieCollection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6e9444ac8f0d90044532ed6da2ed9acec0836c3c
--- /dev/null
+++ b/src/Http/Http.Features/src/IRequestCookieCollection.cs
@@ -0,0 +1,87 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Represents the HttpRequest cookie collection
+    /// </summary>
+    public interface IRequestCookieCollection : IEnumerable<KeyValuePair<string, string>>
+    {
+        /// <summary>
+        ///     Gets the number of elements contained in the <see cref="IRequestCookieCollection" />.
+        /// </summary>
+        /// <returns>
+        ///     The number of elements contained in the <see cref="IRequestCookieCollection" />.
+        /// </returns>
+        int Count { get; }
+
+        /// <summary>
+        ///     Gets an <see cref="ICollection{T}" /> containing the keys of the
+        ///     <see cref="IRequestCookieCollection" />.
+        /// </summary>
+        /// <returns>
+        ///     An <see cref="ICollection{T}" /> containing the keys of the object
+        ///     that implements <see cref="IRequestCookieCollection" />.
+        /// </returns>
+        ICollection<string> Keys { get; }
+
+        /// <summary>
+        ///     Determines whether the <see cref="IRequestCookieCollection" /> contains an element
+        ///     with the specified key.
+        /// </summary>
+        /// <param name="key">
+        /// The key to locate in the <see cref="IRequestCookieCollection" />.
+        /// </param>
+        /// <returns>
+        ///     true if the <see cref="IRequestCookieCollection" /> contains an element with
+        ///     the key; otherwise, false.
+        /// </returns>
+        /// <exception cref="System.ArgumentNullException">
+        ///     key is null.
+        /// </exception>
+        bool ContainsKey(string key);
+
+        /// <summary>
+        ///    Gets the value associated with the specified key.
+        /// </summary>
+        /// <param name="key">
+        ///     The key of the value to get.
+        /// </param>
+        /// <param name="value">
+        ///     The key of the value to get.
+        ///     When this method returns, the value associated with the specified key, if the
+        ///     key is found; otherwise, the default value for the type of the value parameter.
+        ///     This parameter is passed uninitialized.
+        /// </param>
+        /// <returns>
+        ///    true if the object that implements <see cref="IRequestCookieCollection" /> contains
+        ///     an element with the specified key; otherwise, false.
+        /// </returns>
+        /// <exception cref="System.ArgumentNullException">
+        ///     key is null.
+        /// </exception>
+        bool TryGetValue(string key, out string value);
+
+        /// <summary>
+        ///     Gets the value with the specified key.
+        /// </summary>
+        /// <param name="key">
+        ///     The key of the value to get.
+        /// </param>
+        /// <returns>
+        ///     The element with the specified key, or <c>string.Empty</c> if the key is not present.
+        /// </returns>
+        /// <exception cref="System.ArgumentNullException">
+        ///     key is null.
+        /// </exception>
+        /// <remarks>
+        ///     <see cref="IRequestCookieCollection" /> has a different indexer contract than
+        ///     <see cref="IDictionary{TKey, TValue}" />, as it will return <c>string.Empty</c> for missing entries
+        ///     rather than throwing an Exception.
+        /// </remarks>
+        string this[string key] { get; }
+    }
+}
diff --git a/src/Http/Http.Features/src/IRequestCookiesFeature.cs b/src/Http/Http.Features/src/IRequestCookiesFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..55ba6036423899b4f92dba886413e2203f11a5a7
--- /dev/null
+++ b/src/Http/Http.Features/src/IRequestCookiesFeature.cs
@@ -0,0 +1,10 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public interface IRequestCookiesFeature
+    {
+        IRequestCookieCollection Cookies { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/IResponseCookies.cs b/src/Http/Http.Features/src/IResponseCookies.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9c8c3b42bab90a0e50947ecded06233c2d515f2b
--- /dev/null
+++ b/src/Http/Http.Features/src/IResponseCookies.cs
@@ -0,0 +1,42 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// A wrapper for the response Set-Cookie header.
+    /// </summary>
+    public interface IResponseCookies
+    {
+        /// <summary>
+        /// Add a new cookie and value.
+        /// </summary>
+        /// <param name="key">Name of the new cookie.</param>
+        /// <param name="value">Value of the new cookie.</param>
+        void Append(string key, string value);
+
+        /// <summary>
+        /// Add a new cookie.
+        /// </summary>
+        /// <param name="key">Name of the new cookie.</param>
+        /// <param name="value">Value of the new cookie.</param>
+        /// <param name="options"><see cref="CookieOptions"/> included in the new cookie setting.</param>
+        void Append(string key, string value, CookieOptions options);
+
+        /// <summary>
+        /// Sets an expired cookie.
+        /// </summary>
+        /// <param name="key">Name of the cookie to expire.</param>
+        void Delete(string key);
+
+        /// <summary>
+        /// Sets an expired cookie.
+        /// </summary>
+        /// <param name="key">Name of the cookie to expire.</param>
+        /// <param name="options">
+        /// <see cref="CookieOptions"/> used to discriminate the particular cookie to expire. The
+        /// <see cref="CookieOptions.Domain"/> and <see cref="CookieOptions.Path"/> values are especially important.
+        /// </param>
+        void Delete(string key, CookieOptions options);
+    }
+}
diff --git a/src/Http/Http.Features/src/IResponseCookiesFeature.cs b/src/Http/Http.Features/src/IResponseCookiesFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7ce10418400d3416525d2b710d32c9ee0a11346c
--- /dev/null
+++ b/src/Http/Http.Features/src/IResponseCookiesFeature.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    /// <summary>
+    /// A helper for creating the response Set-Cookie header.
+    /// </summary>
+    public interface IResponseCookiesFeature
+    {
+        /// <summary>
+        /// Gets the wrapper for the response Set-Cookie header.
+        /// </summary>
+        IResponseCookies Cookies { get; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/IServiceProvidersFeature.cs b/src/Http/Http.Features/src/IServiceProvidersFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..aed0fc91defc3a32d2a89094fb6ff65f1135da5e
--- /dev/null
+++ b/src/Http/Http.Features/src/IServiceProvidersFeature.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.
+
+using System;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public interface IServiceProvidersFeature
+    {
+        IServiceProvider RequestServices { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/ISession.cs b/src/Http/Http.Features/src/ISession.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6bd780684d1db65228cc9ed13df0c638ad8d162b
--- /dev/null
+++ b/src/Http/Http.Features/src/ISession.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.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public interface ISession
+    {
+        /// <summary>
+        /// Indicate whether the current session has loaded.
+        /// </summary>
+        bool IsAvailable { get; }
+
+        /// <summary>
+        /// A unique identifier for the current session. This is not the same as the session cookie
+        /// since the cookie lifetime may not be the same as the session entry lifetime in the data store.
+        /// </summary>
+        string Id { get; }
+
+        /// <summary>
+        /// Enumerates all the keys, if any.
+        /// </summary>
+        IEnumerable<string> Keys { get; }
+
+        /// <summary>
+        /// Load the session from the data store. This may throw if the data store is unavailable.
+        /// </summary>
+        /// <returns></returns>
+        Task LoadAsync(CancellationToken cancellationToken = default(CancellationToken));
+
+        /// <summary>
+        /// Store the session in the data store. This may throw if the data store is unavailable.
+        /// </summary>
+        /// <returns></returns>
+        Task CommitAsync(CancellationToken cancellationToken = default(CancellationToken));
+
+        /// <summary>
+        /// Retrieve the value of the given key, if present.
+        /// </summary>
+        /// <param name="key"></param>
+        /// <param name="value"></param>
+        /// <returns></returns>
+        bool TryGetValue(string key, out byte[] value);
+
+        /// <summary>
+        /// Set the given key and value in the current session. This will throw if the session
+        /// was not established prior to sending the response.
+        /// </summary>
+        /// <param name="key"></param>
+        /// <param name="value"></param>
+        void Set(string key, byte[] value);
+
+        /// <summary>
+        /// Remove the given key from the session if present.
+        /// </summary>
+        /// <param name="key"></param>
+        void Remove(string key);
+
+        /// <summary>
+        /// Remove all entries from the current session, if any.
+        /// The session cookie is not removed.
+        /// </summary>
+        void Clear();
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/ISessionFeature.cs b/src/Http/Http.Features/src/ISessionFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..23652994158300c819349b68a0173e5108c5abb1
--- /dev/null
+++ b/src/Http/Http.Features/src/ISessionFeature.cs
@@ -0,0 +1,10 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public interface ISessionFeature
+    {
+        ISession Session { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/ITlsConnectionFeature.cs b/src/Http/Http.Features/src/ITlsConnectionFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c34a3339d5293bd0cf0ed881d7113b1d192eaa2d
--- /dev/null
+++ b/src/Http/Http.Features/src/ITlsConnectionFeature.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public interface ITlsConnectionFeature
+    {
+        /// <summary>
+        /// Synchronously retrieves the client certificate, if any.
+        /// </summary>
+        X509Certificate2 ClientCertificate { get; set; }
+
+        /// <summary>
+        /// Asynchronously retrieves the client certificate, if any.
+        /// </summary>
+        /// <returns></returns>
+        Task<X509Certificate2> GetClientCertificateAsync(CancellationToken cancellationToken);
+    }
+}
diff --git a/src/Http/Http.Features/src/ITlsTokenBindingFeature.cs b/src/Http/Http.Features/src/ITlsTokenBindingFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d63333dd0a839003ec170a0910b3e72508467537
--- /dev/null
+++ b/src/Http/Http.Features/src/ITlsTokenBindingFeature.cs
@@ -0,0 +1,35 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    /// <summary>
+    /// Provides information regarding TLS token binding parameters.
+    /// </summary>
+    /// <remarks>
+    /// TLS token bindings help mitigate the risk of impersonation by an attacker in the
+    /// event an authenticated client's bearer tokens are somehow exfiltrated from the
+    /// client's machine. See https://datatracker.ietf.org/doc/draft-popov-token-binding/
+    /// for more information.
+    /// </remarks>
+    public interface ITlsTokenBindingFeature
+    {
+        /// <summary>
+        /// Gets the 'provided' token binding identifier associated with the request.
+        /// </summary>
+        /// <returns>The token binding identifier, or null if the client did not
+        /// supply a 'provided' token binding or valid proof of possession of the
+        /// associated private key. The caller should treat this identifier as an
+        /// opaque blob and should not try to parse it.</returns>
+        byte[] GetProvidedTokenBindingId();
+
+        /// <summary>
+        /// Gets the 'referred' token binding identifier associated with the request.
+        /// </summary>
+        /// <returns>The token binding identifier, or null if the client did not
+        /// supply a 'referred' token binding or valid proof of possession of the
+        /// associated private key. The caller should treat this identifier as an
+        /// opaque blob and should not try to parse it.</returns>
+        byte[] GetReferredTokenBindingId();
+    }
+}
diff --git a/src/Http/Http.Features/src/ITrackingConsentFeature.cs b/src/Http/Http.Features/src/ITrackingConsentFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e7fbeaeaf338cba8b5a742eb5355f694775f27a2
--- /dev/null
+++ b/src/Http/Http.Features/src/ITrackingConsentFeature.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.
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    /// <summary>
+    /// Used to query, grant, and withdraw user consent regarding the storage of user
+    /// information related to site activity and functionality.
+    /// </summary>
+    public interface ITrackingConsentFeature
+    {
+        /// <summary>
+        /// Indicates if consent is required for the given request.
+        /// </summary>
+        bool IsConsentNeeded { get; }
+
+        /// <summary>
+        /// Indicates if consent was given.
+        /// </summary>
+        bool HasConsent { get; }
+
+        /// <summary>
+        /// Indicates either if consent has been given or if consent is not required.
+        /// </summary>
+        bool CanTrack { get; }
+
+        /// <summary>
+        /// Grants consent for this request. If the response has not yet started then
+        /// this will also grant consent for future requests.
+        /// </summary>
+        void GrantConsent();
+
+        /// <summary>
+        /// Withdraws consent for this request. If the response has not yet started then
+        /// this will also withdraw consent for future requests.
+        /// </summary>
+        void WithdrawConsent();
+
+        /// <summary>
+        /// Creates a consent cookie for use when granting consent from a javascript client.
+        /// </summary>
+        string CreateConsentCookie();
+    }
+}
diff --git a/src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj b/src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..7a2310a6fd16f9e496ada2288d45a20575049345
--- /dev/null
+++ b/src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>ASP.NET Core HTTP feature interface definitions.</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <NoWarn>$(NoWarn);CS1591</NoWarn>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore</PackageTags>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.Extensions.Primitives" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/Http.Features/src/SameSiteMode.cs b/src/Http/Http.Features/src/SameSiteMode.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0ae4481e3d241162ed6d14d67ab84c9e721640d3
--- /dev/null
+++ b/src/Http/Http.Features/src/SameSiteMode.cs
@@ -0,0 +1,14 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http
+{
+    // RFC Draft: https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00
+    // This mirrors Microsoft.Net.Http.Headers.SameSiteMode
+    public enum SameSiteMode
+    {
+        None = 0,
+        Lax,
+        Strict
+    }
+}
diff --git a/src/Http/Http.Features/src/WebSocketAcceptContext.cs b/src/Http/Http.Features/src/WebSocketAcceptContext.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5e3659d6478413c9588005c2fdd0bef39247e168
--- /dev/null
+++ b/src/Http/Http.Features/src/WebSocketAcceptContext.cs
@@ -0,0 +1,10 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http
+{
+    public class WebSocketAcceptContext
+    {
+        public virtual string SubProtocol { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/src/baseline.netcore.json b/src/Http/Http.Features/src/baseline.netcore.json
new file mode 100644
index 0000000000000000000000000000000000000000..6af2ceccf9c00029a6aa1278e7a695bdf6f011a0
--- /dev/null
+++ b/src/Http/Http.Features/src/baseline.netcore.json
@@ -0,0 +1,2727 @@
+{
+  "AssemblyIdentity": "Microsoft.AspNetCore.Http.Features, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+  "Types": [
+    {
+      "Name": "Microsoft.AspNetCore.Http.CookieOptions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Domain",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Domain",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Path",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Path",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Expires",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.DateTimeOffset>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Expires",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.DateTimeOffset>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Secure",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Secure",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_SameSite",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.SameSiteMode",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_SameSite",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.SameSiteMode"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HttpOnly",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_HttpOnly",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MaxAge",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.TimeSpan>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_MaxAge",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.TimeSpan>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IsEssential",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_IsEssential",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.IFormCollection",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [
+        "System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>>"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Count",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Keys",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.ICollection<System.String>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ContainsKey",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryGetValue",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringValues",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Item",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringValues",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Files",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IFormFileCollection",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.IFormFile",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_ContentType",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentDisposition",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Headers",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IHeaderDictionary",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Length",
+          "Parameters": [],
+          "ReturnType": "System.Int64",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Name",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_FileName",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OpenReadStream",
+          "Parameters": [],
+          "ReturnType": "System.IO.Stream",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "CopyTo",
+          "Parameters": [
+            {
+              "Name": "target",
+              "Type": "System.IO.Stream"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "CopyToAsync",
+          "Parameters": [
+            {
+              "Name": "target",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.IFormFileCollection",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [
+        "System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Http.IFormFile>"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Item",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.IFormFile",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetFile",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.IFormFile",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetFiles",
+          "Parameters": [
+            {
+              "Name": "name",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.IReadOnlyList<Microsoft.AspNetCore.Http.IFormFile>",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.IHeaderDictionary",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [
+        "System.Collections.Generic.IDictionary<System.String, Microsoft.Extensions.Primitives.StringValues>"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Item",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringValues",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Item",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringValues"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentLength",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Int64>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentLength",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.Int64>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.IQueryCollection",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [
+        "System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>>"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Count",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Keys",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.ICollection<System.String>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ContainsKey",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryGetValue",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringValues",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Item",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringValues",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.IRequestCookieCollection",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [
+        "System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.String, System.String>>"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Count",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Keys",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.ICollection<System.String>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ContainsKey",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryGetValue",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.String",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Item",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.IResponseCookies",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Append",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Append",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.String"
+            },
+            {
+              "Name": "options",
+              "Type": "Microsoft.AspNetCore.Http.CookieOptions"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Delete",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Delete",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "options",
+              "Type": "Microsoft.AspNetCore.Http.CookieOptions"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.ISession",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_IsAvailable",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Id",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Keys",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IEnumerable<System.String>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "LoadAsync",
+          "Parameters": [
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "CommitAsync",
+          "Parameters": [
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryGetValue",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.Byte[]",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Set",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.Byte[]"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Remove",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Clear",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.SameSiteMode",
+      "Visibility": "Public",
+      "Kind": "Enumeration",
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Field",
+          "Name": "None",
+          "Parameters": [],
+          "GenericParameter": [],
+          "Literal": "0"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Lax",
+          "Parameters": [],
+          "GenericParameter": [],
+          "Literal": "1"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Strict",
+          "Parameters": [],
+          "GenericParameter": [],
+          "Literal": "2"
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.WebSocketAcceptContext",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_SubProtocol",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_SubProtocol",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.FeatureCollection",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "GetEnumerator",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<System.Type, System.Object>>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.Type, System.Object>>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Revision",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IsReadOnly",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Item",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.Type"
+            }
+          ],
+          "ReturnType": "System.Object",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Item",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.Type"
+            },
+            {
+              "Name": "value",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Get<T0>",
+          "Parameters": [],
+          "ReturnType": "T0",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "TFeature",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Method",
+          "Name": "Set<T0>",
+          "Parameters": [
+            {
+              "Name": "instance",
+              "Type": "T0"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "TFeature",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "defaults",
+              "Type": "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.FeatureReference<T0>",
+      "Visibility": "Public",
+      "Kind": "Struct",
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Fetch",
+          "Parameters": [
+            {
+              "Name": "features",
+              "Type": "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+            }
+          ],
+          "ReturnType": "T0",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Update",
+          "Parameters": [
+            {
+              "Name": "features",
+              "Type": "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+            },
+            {
+              "Name": "feature",
+              "Type": "T0"
+            }
+          ],
+          "ReturnType": "T0",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Default",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.Features.FeatureReference<T0>",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": [
+        {
+          "ParameterName": "T",
+          "ParameterPosition": 0,
+          "BaseTypeOrInterfaces": []
+        }
+      ]
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.FeatureReferences<T0>",
+      "Visibility": "Public",
+      "Kind": "Struct",
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Collection",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Revision",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Fetch<T0, T1>",
+          "Parameters": [
+            {
+              "Name": "cached",
+              "Type": "T0",
+              "Direction": "Ref"
+            },
+            {
+              "Name": "state",
+              "Type": "T1"
+            },
+            {
+              "Name": "factory",
+              "Type": "System.Func<T1, T0>"
+            }
+          ],
+          "ReturnType": "T0",
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "TFeature",
+              "ParameterPosition": 0,
+              "Class": true,
+              "BaseTypeOrInterfaces": []
+            },
+            {
+              "ParameterName": "TState",
+              "ParameterPosition": 1,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Method",
+          "Name": "Fetch<T0>",
+          "Parameters": [
+            {
+              "Name": "cached",
+              "Type": "T0",
+              "Direction": "Ref"
+            },
+            {
+              "Name": "factory",
+              "Type": "System.Func<Microsoft.AspNetCore.Http.Features.IFeatureCollection, T0>"
+            }
+          ],
+          "ReturnType": "T0",
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "TFeature",
+              "ParameterPosition": 0,
+              "Class": true,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "collection",
+              "Type": "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Cache",
+          "Parameters": [],
+          "ReturnType": "T0",
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": [
+        {
+          "ParameterName": "TCache",
+          "ParameterPosition": 0,
+          "BaseTypeOrInterfaces": []
+        }
+      ]
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [
+        "System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.Type, System.Object>>"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_IsReadOnly",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Revision",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Item",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.Type"
+            }
+          ],
+          "ReturnType": "System.Object",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Item",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.Type"
+            },
+            {
+              "Name": "value",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Get<T0>",
+          "Parameters": [],
+          "ReturnType": "T0",
+          "GenericParameter": [
+            {
+              "ParameterName": "TFeature",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Method",
+          "Name": "Set<T0>",
+          "Parameters": [
+            {
+              "Name": "instance",
+              "Type": "T0"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": [
+            {
+              "ParameterName": "TFeature",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IFormFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_HasFormContentType",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Form",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IFormCollection",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Form",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.IFormCollection"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ReadForm",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IFormCollection",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ReadFormAsync",
+          "Parameters": [
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Http.IFormCollection>",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IHttpBodyControlFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_AllowSynchronousIO",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_AllowSynchronousIO",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IHttpBufferingFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "DisableRequestBuffering",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "DisableResponseBuffering",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_ConnectionId",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ConnectionId",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_RemoteIpAddress",
+          "Parameters": [],
+          "ReturnType": "System.Net.IPAddress",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RemoteIpAddress",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Net.IPAddress"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_LocalIpAddress",
+          "Parameters": [],
+          "ReturnType": "System.Net.IPAddress",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_LocalIpAddress",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Net.IPAddress"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_RemotePort",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RemotePort",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_LocalPort",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_LocalPort",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IHttpMaxRequestBodySizeFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_IsReadOnly",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MaxRequestBodySize",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Int64>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_MaxRequestBodySize",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.Int64>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Protocol",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Protocol",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Scheme",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Scheme",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Method",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Method",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_PathBase",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_PathBase",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Path",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Path",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_QueryString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_QueryString",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_RawTarget",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RawTarget",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Headers",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IHeaderDictionary",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Headers",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.IHeaderDictionary"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Body",
+          "Parameters": [],
+          "ReturnType": "System.IO.Stream",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Body",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.IO.Stream"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IHttpRequestIdentifierFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_TraceIdentifier",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_TraceIdentifier",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IHttpRequestLifetimeFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_RequestAborted",
+          "Parameters": [],
+          "ReturnType": "System.Threading.CancellationToken",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RequestAborted",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Abort",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IHttpResponseFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_StatusCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_StatusCode",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ReasonPhrase",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ReasonPhrase",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Headers",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IHeaderDictionary",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Headers",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.IHeaderDictionary"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Body",
+          "Parameters": [],
+          "ReturnType": "System.IO.Stream",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Body",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.IO.Stream"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HasStarted",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnStarting",
+          "Parameters": [
+            {
+              "Name": "callback",
+              "Type": "System.Func<System.Object, System.Threading.Tasks.Task>"
+            },
+            {
+              "Name": "state",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnCompleted",
+          "Parameters": [
+            {
+              "Name": "callback",
+              "Type": "System.Func<System.Object, System.Threading.Tasks.Task>"
+            },
+            {
+              "Name": "state",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "SendFileAsync",
+          "Parameters": [
+            {
+              "Name": "path",
+              "Type": "System.String"
+            },
+            {
+              "Name": "offset",
+              "Type": "System.Int64"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Nullable<System.Int64>"
+            },
+            {
+              "Name": "cancellation",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IHttpUpgradeFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_IsUpgradableRequest",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UpgradeAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<System.IO.Stream>",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IHttpWebSocketFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_IsWebSocketRequest",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AcceptAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.WebSocketAcceptContext"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Net.WebSockets.WebSocket>",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IItemsFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Items",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.Object, System.Object>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Items",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Collections.Generic.IDictionary<System.Object, System.Object>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IQueryFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Query",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IQueryCollection",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Query",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.IQueryCollection"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IRequestCookiesFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Cookies",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IRequestCookieCollection",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Cookies",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.IRequestCookieCollection"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IResponseCookiesFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Cookies",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IResponseCookies",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.IServiceProvidersFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_RequestServices",
+          "Parameters": [],
+          "ReturnType": "System.IServiceProvider",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RequestServices",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.IServiceProvider"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.ISessionFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Session",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.ISession",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Session",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.ISession"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.ITlsConnectionFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_ClientCertificate",
+          "Parameters": [],
+          "ReturnType": "System.Security.Cryptography.X509Certificates.X509Certificate2",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ClientCertificate",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Security.Cryptography.X509Certificates.X509Certificate2"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetClientCertificateAsync",
+          "Parameters": [
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Security.Cryptography.X509Certificates.X509Certificate2>",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.ITlsTokenBindingFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "GetProvidedTokenBindingId",
+          "Parameters": [],
+          "ReturnType": "System.Byte[]",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetReferredTokenBindingId",
+          "Parameters": [],
+          "ReturnType": "System.Byte[]",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.ITrackingConsentFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_IsConsentNeeded",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HasConsent",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_CanTrack",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GrantConsent",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "WithdrawConsent",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "CreateConsentCookie",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.Authentication.AuthenticateContext",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_AuthenticationScheme",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Accepted",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Principal",
+          "Parameters": [],
+          "ReturnType": "System.Security.Claims.ClaimsPrincipal",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Properties",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.String, System.String>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Description",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.String, System.Object>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Error",
+          "Parameters": [],
+          "ReturnType": "System.Exception",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Authenticated",
+          "Parameters": [
+            {
+              "Name": "principal",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            },
+            {
+              "Name": "properties",
+              "Type": "System.Collections.Generic.IDictionary<System.String, System.String>"
+            },
+            {
+              "Name": "description",
+              "Type": "System.Collections.Generic.IDictionary<System.String, System.Object>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "NotAuthenticated",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Failed",
+          "Parameters": [
+            {
+              "Name": "error",
+              "Type": "System.Exception"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.Authentication.ChallengeBehavior",
+      "Visibility": "Public",
+      "Kind": "Enumeration",
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Field",
+          "Name": "Automatic",
+          "Parameters": [],
+          "GenericParameter": [],
+          "Literal": "0"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Unauthorized",
+          "Parameters": [],
+          "GenericParameter": [],
+          "Literal": "1"
+        },
+        {
+          "Kind": "Field",
+          "Name": "Forbidden",
+          "Parameters": [],
+          "GenericParameter": [],
+          "Literal": "2"
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.Authentication.ChallengeContext",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_AuthenticationScheme",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Behavior",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.Features.Authentication.ChallengeBehavior",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Properties",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.String, System.String>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Accepted",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Accept",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "properties",
+              "Type": "System.Collections.Generic.IDictionary<System.String, System.String>"
+            },
+            {
+              "Name": "behavior",
+              "Type": "Microsoft.AspNetCore.Http.Features.Authentication.ChallengeBehavior"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.Authentication.DescribeSchemesContext",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Results",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IEnumerable<System.Collections.Generic.IDictionary<System.String, System.Object>>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Accept",
+          "Parameters": [
+            {
+              "Name": "description",
+              "Type": "System.Collections.Generic.IDictionary<System.String, System.Object>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.Authentication.IAuthenticationHandler",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "GetDescriptions",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.Features.Authentication.DescribeSchemesContext"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AuthenticateAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.Features.Authentication.AuthenticateContext"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ChallengeAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.Features.Authentication.ChallengeContext"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignInAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.Features.Authentication.SignInContext"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SignOutAsync",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.Features.Authentication.SignOutContext"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.Authentication.IHttpAuthenticationFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_User",
+          "Parameters": [],
+          "ReturnType": "System.Security.Claims.ClaimsPrincipal",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_User",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Handler",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.Features.Authentication.IAuthenticationHandler",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Handler",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.Features.Authentication.IAuthenticationHandler"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.Authentication.SignInContext",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_AuthenticationScheme",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Principal",
+          "Parameters": [],
+          "ReturnType": "System.Security.Claims.ClaimsPrincipal",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Properties",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.String, System.String>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Accepted",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Accept",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "principal",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            },
+            {
+              "Name": "properties",
+              "Type": "System.Collections.Generic.IDictionary<System.String, System.String>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.Authentication.SignOutContext",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_AuthenticationScheme",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Properties",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.String, System.String>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Accepted",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Accept",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "authenticationScheme",
+              "Type": "System.String"
+            },
+            {
+              "Name": "properties",
+              "Type": "System.Collections.Generic.IDictionary<System.String, System.String>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/test/Authentication/AuthenticateContextTest.cs b/src/Http/Http.Features/test/Authentication/AuthenticateContextTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c4d901322e584f2563de51dfc80b84547560bc96
--- /dev/null
+++ b/src/Http/Http.Features/test/Authentication/AuthenticateContextTest.cs
@@ -0,0 +1,162 @@
+// 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.Security.Claims;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Features.Authentication
+{
+    public class AuthenticateContextTest
+    {
+        [Fact]
+        public void AuthenticateContext_Authenticated()
+        {
+            // Arrange
+            var context = new AuthenticateContext("test");
+
+            var principal = new ClaimsPrincipal();
+            var properties = new Dictionary<string, string>();
+            var description = new Dictionary<string, object>();
+
+            // Act
+            context.Authenticated(principal, properties, description);
+
+            // Assert
+            Assert.True(context.Accepted);
+            Assert.Equal("test", context.AuthenticationScheme);
+            Assert.Same(description, context.Description);
+            Assert.Null(context.Error);
+            Assert.Same(principal, context.Principal);
+            Assert.Same(properties, context.Properties);
+        }
+
+        [Fact]
+        public void AuthenticateContext_Authenticated_SetsUnusedPropertiesToDefault()
+        {
+            // Arrange
+            var context = new AuthenticateContext("test");
+
+            var principal = new ClaimsPrincipal();
+            var properties = new Dictionary<string, string>();
+            var description = new Dictionary<string, object>();
+
+            context.Failed(new Exception());
+
+            // Act
+            context.Authenticated(principal, properties, description);
+
+            // Assert
+            Assert.True(context.Accepted);
+            Assert.Equal("test", context.AuthenticationScheme);
+            Assert.Same(description, context.Description);
+            Assert.Null(context.Error);
+            Assert.Same(principal, context.Principal);
+            Assert.Same(properties, context.Properties);
+        }
+
+        [Fact]
+        public void AuthenticateContext_Failed()
+        {
+            // Arrange
+            var context = new AuthenticateContext("test");
+
+            var exception = new Exception();
+
+            // Act
+            context.Failed(exception);
+
+            // Assert
+            Assert.True(context.Accepted);
+            Assert.Equal("test", context.AuthenticationScheme);
+            Assert.Null(context.Description);
+            Assert.Same(exception, context.Error);
+            Assert.Null(context.Principal);
+            Assert.Null(context.Properties);
+        }
+
+        [Fact]
+        public void AuthenticateContext_Failed_SetsUnusedPropertiesToDefault()
+        {
+            // Arrange
+            var context = new AuthenticateContext("test");
+
+            var exception = new Exception();
+
+            context.Authenticated(new ClaimsPrincipal(), new Dictionary<string, string>(), new Dictionary<string, object>());
+
+            // Act
+            context.Failed(exception);
+
+            // Assert
+            Assert.True(context.Accepted);
+            Assert.Equal("test", context.AuthenticationScheme);
+            Assert.Null(context.Description);
+            Assert.Same(exception, context.Error);
+            Assert.Null(context.Principal);
+            Assert.Null(context.Properties);
+        }
+
+        [Fact]
+        public void AuthenticateContext_NotAuthenticated()
+        {
+            // Arrange
+            var context = new AuthenticateContext("test");
+
+            // Act
+            context.NotAuthenticated();
+
+            // Assert
+            Assert.True(context.Accepted);
+            Assert.Equal("test", context.AuthenticationScheme);
+            Assert.Null(context.Description);
+            Assert.Null(context.Error);
+            Assert.Null(context.Principal);
+            Assert.Null(context.Properties);
+        }
+
+        [Fact]
+        public void AuthenticateContext_NotAuthenticated_SetsUnusedPropertiesToDefault_Authenticated()
+        {
+            // Arrange
+            var context = new AuthenticateContext("test");
+
+            var exception = new Exception();
+
+            context.Authenticated(new ClaimsPrincipal(), new Dictionary<string, string>(), new Dictionary<string, object>());
+
+            // Act
+            context.NotAuthenticated();
+
+            // Assert
+            Assert.True(context.Accepted);
+            Assert.Equal("test", context.AuthenticationScheme);
+            Assert.Null(context.Description);
+            Assert.Null(context.Error);
+            Assert.Null(context.Principal);
+            Assert.Null(context.Properties);
+        }
+
+        [Fact]
+        public void AuthenticateContext_NotAuthenticated_SetsUnusedPropertiesToDefault_Failed()
+        {
+            // Arrange
+            var context = new AuthenticateContext("test");
+            
+            context.Failed(new Exception());
+
+            context.NotAuthenticated();
+
+            // Assert
+            Assert.True(context.Accepted);
+            Assert.Equal("test", context.AuthenticationScheme);
+            Assert.Null(context.Description);
+            Assert.Null(context.Error);
+            Assert.Null(context.Principal);
+            Assert.Null(context.Properties);
+        }
+    }
+}
diff --git a/src/Http/Http.Features/test/FeatureCollectionTests.cs b/src/Http/Http.Features/test/FeatureCollectionTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..36ad77f67857670127b98b7f127c5ee5b265320b
--- /dev/null
+++ b/src/Http/Http.Features/test/FeatureCollectionTests.cs
@@ -0,0 +1,48 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class FeatureCollectionTests
+    {
+        [Fact]
+        public void AddedInterfaceIsReturned()
+        {
+            var interfaces = new FeatureCollection();
+            var thing = new Thing();
+
+            interfaces[typeof(IThing)] = thing;
+
+            object thing2 = interfaces[typeof(IThing)];
+            Assert.Equal(thing2, thing);
+        }
+
+        [Fact]
+        public void IndexerAlsoAddsItems()
+        {
+            var interfaces = new FeatureCollection();
+            var thing = new Thing();
+
+            interfaces[typeof(IThing)] = thing;
+
+            Assert.Equal(interfaces[typeof(IThing)], thing);
+        }
+
+        [Fact]
+        public void SetNullValueRemoves()
+        {
+            var interfaces = new FeatureCollection();
+            var thing = new Thing();
+
+            interfaces[typeof(IThing)] = thing;
+            Assert.Equal(interfaces[typeof(IThing)], thing);
+
+            interfaces[typeof(IThing)] = null;
+
+            object thing2 = interfaces[typeof(IThing)];
+            Assert.Null(thing2);
+        }
+    }
+}
diff --git a/src/Http/Http.Features/test/IThing.cs b/src/Http/Http.Features/test/IThing.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f5b0a1e1221a12ed261f6e79f773150e3ba889f7
--- /dev/null
+++ b/src/Http/Http.Features/test/IThing.cs
@@ -0,0 +1,10 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public interface IThing
+    {
+        string Hello();
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http.Features/test/Microsoft.AspNetCore.Http.Features.Tests.csproj b/src/Http/Http.Features/test/Microsoft.AspNetCore.Http.Features.Tests.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..b7c77fc19f2453775e8f79ad0afae87464c9fd71
--- /dev/null
+++ b/src/Http/Http.Features/test/Microsoft.AspNetCore.Http.Features.Tests.csproj
@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Http.Features" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/Http.Features/test/Thing.cs b/src/Http/Http.Features/test/Thing.cs
new file mode 100644
index 0000000000000000000000000000000000000000..27a2c0e2857e7c46cf7dff7fc449f9c8ed0e354c
--- /dev/null
+++ b/src/Http/Http.Features/test/Thing.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.Features
+{
+    public class Thing : IThing
+    {
+        public string Hello()
+        {
+            return "World";
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/Authentication/DefaultAuthenticationManager.cs b/src/Http/Http/src/Authentication/DefaultAuthenticationManager.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9f4121f4cb38cda98a1e7aed7f97d3dce837d42f
--- /dev/null
+++ b/src/Http/Http/src/Authentication/DefaultAuthenticationManager.cs
@@ -0,0 +1,184 @@
+// 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.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Http.Features.Authentication;
+
+namespace Microsoft.AspNetCore.Http.Authentication.Internal
+{
+    [Obsolete("This is obsolete and will be removed in a future version. See https://go.microsoft.com/fwlink/?linkid=845470.")]
+    public class DefaultAuthenticationManager : AuthenticationManager
+    {
+        // Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624
+        private readonly static Func<IFeatureCollection, IHttpAuthenticationFeature> _newAuthenticationFeature = f => new HttpAuthenticationFeature();
+
+        private HttpContext _context;
+        private FeatureReferences<IHttpAuthenticationFeature> _features;
+
+        public DefaultAuthenticationManager(HttpContext context)
+        {
+            Initialize(context);
+        }
+
+        public virtual void Initialize(HttpContext context)
+        {
+            _context = context;
+            _features = new FeatureReferences<IHttpAuthenticationFeature>(context.Features);
+        }
+
+        public virtual void Uninitialize()
+        {
+            _features = default(FeatureReferences<IHttpAuthenticationFeature>);
+        }
+
+        public override HttpContext HttpContext => _context;
+
+        private IHttpAuthenticationFeature HttpAuthenticationFeature =>
+            _features.Fetch(ref _features.Cache, _newAuthenticationFeature);
+
+        public override IEnumerable<AuthenticationDescription> GetAuthenticationSchemes()
+        {
+#pragma warning disable CS0618 // Type or member is obsolete
+            var handler = HttpAuthenticationFeature.Handler;
+#pragma warning restore CS0618 // Type or member is obsolete
+            if (handler == null)
+            {
+                return new AuthenticationDescription[0];
+            }
+
+            var describeContext = new DescribeSchemesContext();
+            handler.GetDescriptions(describeContext);
+            return describeContext.Results.Select(description => new AuthenticationDescription(description));
+        }
+
+        // Remove once callers have been switched to GetAuthenticateInfoAsync
+        public override async Task AuthenticateAsync(AuthenticateContext context)
+        {
+            if (context == null)
+            {
+                throw new ArgumentNullException(nameof(context));
+            }
+
+#pragma warning disable CS0618 // Type or member is obsolete
+            var handler = HttpAuthenticationFeature.Handler;
+#pragma warning restore CS0618 // Type or member is obsolete
+            if (handler != null)
+            {
+                await handler.AuthenticateAsync(context);
+            }
+
+            if (!context.Accepted)
+            {
+                throw new InvalidOperationException($"No authentication handler is configured to authenticate for the scheme: {context.AuthenticationScheme}");
+            }
+        }
+
+        public override async Task<AuthenticateInfo> GetAuthenticateInfoAsync(string authenticationScheme)
+        {
+            if (authenticationScheme == null)
+            {
+                throw new ArgumentNullException(nameof(authenticationScheme));
+            }
+
+#pragma warning disable CS0618 // Type or member is obsolete
+            var handler = HttpAuthenticationFeature.Handler;
+#pragma warning restore CS0618 // Type or member is obsolete
+            var context = new AuthenticateContext(authenticationScheme);
+            if (handler != null)
+            {
+                await handler.AuthenticateAsync(context);
+            }
+
+            if (!context.Accepted)
+            {
+                throw new InvalidOperationException($"No authentication handler is configured to authenticate for the scheme: {context.AuthenticationScheme}");
+            }
+
+            return new AuthenticateInfo
+            {
+                Principal = context.Principal,
+                Properties = new AuthenticationProperties(context.Properties),
+                Description = new AuthenticationDescription(context.Description)
+            };
+        }
+
+        public override async Task ChallengeAsync(string authenticationScheme, AuthenticationProperties properties, ChallengeBehavior behavior)
+        {
+            if (string.IsNullOrEmpty(authenticationScheme))
+            {
+                throw new ArgumentException(nameof(authenticationScheme));
+            }
+
+#pragma warning disable CS0618 // Type or member is obsolete
+            var handler = HttpAuthenticationFeature.Handler;
+#pragma warning restore CS0618 // Type or member is obsolete
+
+            var challengeContext = new ChallengeContext(authenticationScheme, properties?.Items, behavior);
+            if (handler != null)
+            {
+                await handler.ChallengeAsync(challengeContext);
+            }
+
+            if (!challengeContext.Accepted)
+            {
+                throw new InvalidOperationException($"No authentication handler is configured to handle the scheme: {authenticationScheme}");
+            }
+        }
+
+        public override async Task SignInAsync(string authenticationScheme, ClaimsPrincipal principal, AuthenticationProperties properties)
+        {
+            if (string.IsNullOrEmpty(authenticationScheme))
+            {
+                throw new ArgumentException(nameof(authenticationScheme));
+            }
+
+            if (principal == null)
+            {
+                throw new ArgumentNullException(nameof(principal));
+            }
+
+#pragma warning disable CS0618 // Type or member is obsolete
+            var handler = HttpAuthenticationFeature.Handler;
+#pragma warning restore CS0618 // Type or member is obsolete
+
+            var signInContext = new SignInContext(authenticationScheme, principal, properties?.Items);
+            if (handler != null)
+            {
+                await handler.SignInAsync(signInContext);
+            }
+
+            if (!signInContext.Accepted)
+            {
+                throw new InvalidOperationException($"No authentication handler is configured to handle the scheme: {authenticationScheme}");
+            }
+        }
+
+        public override async Task SignOutAsync(string authenticationScheme, AuthenticationProperties properties)
+        {
+            if (string.IsNullOrEmpty(authenticationScheme))
+            {
+                throw new ArgumentException(nameof(authenticationScheme));
+            }
+
+#pragma warning disable CS0618 // Type or member is obsolete
+            var handler = HttpAuthenticationFeature.Handler;
+#pragma warning restore CS0618 // Type or member is obsolete
+
+            var signOutContext = new SignOutContext(authenticationScheme, properties?.Items);
+            if (handler != null)
+            {
+                await handler.SignOutAsync(signOutContext);
+            }
+
+            if (!signOutContext.Accepted)
+            {
+                throw new InvalidOperationException($"No authentication handler is configured to handle the scheme: {authenticationScheme}");
+            }
+        }
+    }
+}
diff --git a/src/Http/Http/src/DefaultHttpContext.cs b/src/Http/Http/src/DefaultHttpContext.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d02ad6322bfc46010a616381ab0cf317168459f5
--- /dev/null
+++ b/src/Http/Http/src/DefaultHttpContext.cs
@@ -0,0 +1,223 @@
+// 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.Security.Claims;
+using System.Threading;
+using Microsoft.AspNetCore.Http.Authentication;
+using Microsoft.AspNetCore.Http.Authentication.Internal;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Http.Features.Authentication;
+using Microsoft.AspNetCore.Http.Internal;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public class DefaultHttpContext : HttpContext
+    {
+        // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
+        private readonly static Func<IFeatureCollection, IItemsFeature> _newItemsFeature = f => new ItemsFeature();
+        private readonly static Func<IFeatureCollection, IServiceProvidersFeature> _newServiceProvidersFeature = f => new ServiceProvidersFeature();
+        private readonly static Func<IFeatureCollection, IHttpAuthenticationFeature> _newHttpAuthenticationFeature = f => new HttpAuthenticationFeature();
+        private readonly static Func<IFeatureCollection, IHttpRequestLifetimeFeature> _newHttpRequestLifetimeFeature = f => new HttpRequestLifetimeFeature();
+        private readonly static Func<IFeatureCollection, ISessionFeature> _newSessionFeature = f => new DefaultSessionFeature();
+        private readonly static Func<IFeatureCollection, ISessionFeature> _nullSessionFeature = f => null;
+        private readonly static Func<IFeatureCollection, IHttpRequestIdentifierFeature> _newHttpRequestIdentifierFeature = f => new HttpRequestIdentifierFeature();
+
+        private FeatureReferences<FeatureInterfaces> _features;
+
+        private HttpRequest _request;
+        private HttpResponse _response;
+
+#pragma warning disable CS0618 // Type or member is obsolete
+        private AuthenticationManager _authenticationManager;
+#pragma warning restore CS0618 // Type or member is obsolete
+
+        private ConnectionInfo _connection;
+        private WebSocketManager _websockets;
+
+        public DefaultHttpContext()
+            : this(new FeatureCollection())
+        {
+            Features.Set<IHttpRequestFeature>(new HttpRequestFeature());
+            Features.Set<IHttpResponseFeature>(new HttpResponseFeature());
+        }
+
+        public DefaultHttpContext(IFeatureCollection features)
+        {
+            Initialize(features);
+        }
+
+        public virtual void Initialize(IFeatureCollection features)
+        {
+            _features = new FeatureReferences<FeatureInterfaces>(features);
+            _request = InitializeHttpRequest();
+            _response = InitializeHttpResponse();
+        }
+
+        public virtual void Uninitialize()
+        {
+            _features = default(FeatureReferences<FeatureInterfaces>);
+            if (_request != null)
+            {
+                UninitializeHttpRequest(_request);
+                _request = null;
+            }
+            if (_response != null)
+            {
+                UninitializeHttpResponse(_response);
+                _response = null;
+            }
+            if (_authenticationManager != null)
+            {
+#pragma warning disable CS0618 // Type or member is obsolete
+                UninitializeAuthenticationManager(_authenticationManager);
+#pragma warning restore CS0618 // Type or member is obsolete
+                _authenticationManager = null;
+            }
+            if (_connection != null)
+            {
+                UninitializeConnectionInfo(_connection);
+                _connection = null;
+            }
+            if (_websockets != null)
+            {
+                UninitializeWebSocketManager(_websockets);
+                _websockets = null;
+            }
+        }
+
+        private IItemsFeature ItemsFeature =>
+            _features.Fetch(ref _features.Cache.Items, _newItemsFeature);
+
+        private IServiceProvidersFeature ServiceProvidersFeature =>
+            _features.Fetch(ref _features.Cache.ServiceProviders, _newServiceProvidersFeature);
+
+        private IHttpAuthenticationFeature HttpAuthenticationFeature =>
+            _features.Fetch(ref _features.Cache.Authentication, _newHttpAuthenticationFeature);
+
+        private IHttpRequestLifetimeFeature LifetimeFeature =>
+            _features.Fetch(ref _features.Cache.Lifetime, _newHttpRequestLifetimeFeature);
+
+        private ISessionFeature SessionFeature =>
+            _features.Fetch(ref _features.Cache.Session, _newSessionFeature);
+
+        private ISessionFeature SessionFeatureOrNull =>
+            _features.Fetch(ref _features.Cache.Session, _nullSessionFeature);
+
+
+        private IHttpRequestIdentifierFeature RequestIdentifierFeature =>
+            _features.Fetch(ref _features.Cache.RequestIdentifier, _newHttpRequestIdentifierFeature);
+
+        public override IFeatureCollection Features => _features.Collection;
+
+        public override HttpRequest Request => _request;
+
+        public override HttpResponse Response => _response;
+
+        public override ConnectionInfo Connection => _connection ?? (_connection = InitializeConnectionInfo());
+
+        /// <summary>
+        /// This is obsolete and will be removed in a future version. 
+        /// The recommended alternative is to use Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions.
+        /// See https://go.microsoft.com/fwlink/?linkid=845470.
+        /// </summary>
+        [Obsolete("This is obsolete and will be removed in a future version. The recommended alternative is to use Microsoft.AspNetCore.Authentication.AuthenticationHttpContextExtensions. See https://go.microsoft.com/fwlink/?linkid=845470.")]
+        public override AuthenticationManager Authentication => _authenticationManager ?? (_authenticationManager = InitializeAuthenticationManager());
+
+        public override WebSocketManager WebSockets => _websockets ?? (_websockets = InitializeWebSocketManager());
+
+
+        public override ClaimsPrincipal User
+        {
+            get
+            {
+                var user = HttpAuthenticationFeature.User;
+                if (user == null)
+                {
+                    user = new ClaimsPrincipal(new ClaimsIdentity());
+                    HttpAuthenticationFeature.User = user;
+                }
+                return user;
+            }
+            set { HttpAuthenticationFeature.User = value; }
+        }
+
+        public override IDictionary<object, object> Items
+        {
+            get { return ItemsFeature.Items; }
+            set { ItemsFeature.Items = value; }
+        }
+
+        public override IServiceProvider RequestServices
+        {
+            get { return ServiceProvidersFeature.RequestServices; }
+            set { ServiceProvidersFeature.RequestServices = value; }
+        }
+
+        public override CancellationToken RequestAborted
+        {
+            get { return LifetimeFeature.RequestAborted; }
+            set { LifetimeFeature.RequestAborted = value; }
+        }
+
+        public override string TraceIdentifier
+        {
+            get { return RequestIdentifierFeature.TraceIdentifier; }
+            set { RequestIdentifierFeature.TraceIdentifier = value; }
+        }
+
+        public override ISession Session
+        {
+            get
+            {
+                var feature = SessionFeatureOrNull;
+                if (feature == null)
+                {
+                    throw new InvalidOperationException("Session has not been configured for this application " +
+                        "or request.");
+                }
+                return feature.Session;
+            }
+            set
+            {
+                SessionFeature.Session = value;
+            }
+        }
+
+
+
+        public override void Abort()
+        {
+            LifetimeFeature.Abort();
+        }
+
+
+        protected virtual HttpRequest InitializeHttpRequest() => new DefaultHttpRequest(this);
+        protected virtual void UninitializeHttpRequest(HttpRequest instance) { }
+
+        protected virtual HttpResponse InitializeHttpResponse() => new DefaultHttpResponse(this);
+        protected virtual void UninitializeHttpResponse(HttpResponse instance) { }
+
+        protected virtual ConnectionInfo InitializeConnectionInfo() => new DefaultConnectionInfo(Features);
+        protected virtual void UninitializeConnectionInfo(ConnectionInfo instance) { }
+
+        [Obsolete("This is obsolete and will be removed in a future version. See https://go.microsoft.com/fwlink/?linkid=845470.")]
+        protected virtual AuthenticationManager InitializeAuthenticationManager() => new DefaultAuthenticationManager(this);
+        [Obsolete("This is obsolete and will be removed in a future version. See https://go.microsoft.com/fwlink/?linkid=845470.")]
+        protected virtual void UninitializeAuthenticationManager(AuthenticationManager instance) { }
+
+        protected virtual WebSocketManager InitializeWebSocketManager() => new DefaultWebSocketManager(Features);
+        protected virtual void UninitializeWebSocketManager(WebSocketManager instance) { }
+
+        struct FeatureInterfaces
+        {
+            public IItemsFeature Items;
+            public IServiceProvidersFeature ServiceProviders;
+            public IHttpAuthenticationFeature Authentication;
+            public IHttpRequestLifetimeFeature Lifetime;
+            public ISessionFeature Session;
+            public IHttpRequestIdentifierFeature RequestIdentifier;
+        }
+    }
+}
diff --git a/src/Http/Http/src/Extensions/HttpRequestRewindExtensions.cs b/src/Http/Http/src/Extensions/HttpRequestRewindExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..557ee42155074a7e33c4a3f77067ebc43be40c9e
--- /dev/null
+++ b/src/Http/Http/src/Extensions/HttpRequestRewindExtensions.cs
@@ -0,0 +1,91 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Http.Internal;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Extension methods for enabling buffering in an <see cref="HttpRequest"/>.
+    /// </summary>
+    public static class HttpRequestRewindExtensions
+    {
+        /// <summary>
+        /// Ensure the <paramref name="request"/> <see cref="HttpRequest.Body"/> can be read multiple times. Normally
+        /// buffers request bodies in memory; writes requests larger than 30K bytes to disk.
+        /// </summary>
+        /// <param name="request">The <see cref="HttpRequest"/> to prepare.</param>
+        /// <remarks>
+        /// Temporary files for larger requests are written to the location named in the <c>ASPNETCORE_TEMP</c>
+        /// environment variable, if any. If that environment variable is not defined, these files are written to the
+        /// current user's temporary folder. Files are automatically deleted at the end of their associated requests.
+        /// </remarks>
+        public static void EnableBuffering(this HttpRequest request)
+        {
+            BufferingHelper.EnableRewind(request);
+        }
+
+        /// <summary>
+        /// Ensure the <paramref name="request"/> <see cref="HttpRequest.Body"/> can be read multiple times. Normally
+        /// buffers request bodies in memory; writes requests larger than <paramref name="bufferThreshold"/> bytes to
+        /// disk.
+        /// </summary>
+        /// <param name="request">The <see cref="HttpRequest"/> to prepare.</param>
+        /// <param name="bufferThreshold">
+        /// The maximum size in bytes of the in-memory <see cref="System.Buffers.ArrayPool{Byte}"/> used to buffer the
+        /// stream. Larger request bodies are written to disk.
+        /// </param>
+        /// <remarks>
+        /// Temporary files for larger requests are written to the location named in the <c>ASPNETCORE_TEMP</c>
+        /// environment variable, if any. If that environment variable is not defined, these files are written to the
+        /// current user's temporary folder. Files are automatically deleted at the end of their associated requests.
+        /// </remarks>
+        public static void EnableBuffering(this HttpRequest request, int bufferThreshold)
+        {
+            BufferingHelper.EnableRewind(request, bufferThreshold);
+        }
+
+        /// <summary>
+        /// Ensure the <paramref name="request"/> <see cref="HttpRequest.Body"/> can be read multiple times. Normally
+        /// buffers request bodies in memory; writes requests larger than 30K bytes to disk.
+        /// </summary>
+        /// <param name="request">The <see cref="HttpRequest"/> to prepare.</param>
+        /// <param name="bufferLimit">
+        /// The maximum size in bytes of the request body. An attempt to read beyond this limit will cause an
+        /// <see cref="System.IO.IOException"/>.
+        /// </param>
+        /// <remarks>
+        /// Temporary files for larger requests are written to the location named in the <c>ASPNETCORE_TEMP</c>
+        /// environment variable, if any. If that environment variable is not defined, these files are written to the
+        /// current user's temporary folder. Files are automatically deleted at the end of their associated requests.
+        /// </remarks>
+        public static void EnableBuffering(this HttpRequest request, long bufferLimit)
+        {
+            BufferingHelper.EnableRewind(request, bufferLimit: bufferLimit);
+        }
+
+        /// <summary>
+        /// Ensure the <paramref name="request"/> <see cref="HttpRequest.Body"/> can be read multiple times. Normally
+        /// buffers request bodies in memory; writes requests larger than <paramref name="bufferThreshold"/> bytes to
+        /// disk.
+        /// </summary>
+        /// <param name="request">The <see cref="HttpRequest"/> to prepare.</param>
+        /// <param name="bufferThreshold">
+        /// The maximum size in bytes of the in-memory <see cref="System.Buffers.ArrayPool{Byte}"/> used to buffer the
+        /// stream. Larger request bodies are written to disk.
+        /// </param>
+        /// <param name="bufferLimit">
+        /// The maximum size in bytes of the request body. An attempt to read beyond this limit will cause an
+        /// <see cref="System.IO.IOException"/>.
+        /// </param>
+        /// <remarks>
+        /// Temporary files for larger requests are written to the location named in the <c>ASPNETCORE_TEMP</c>
+        /// environment variable, if any. If that environment variable is not defined, these files are written to the
+        /// current user's temporary folder. Files are automatically deleted at the end of their associated requests.
+        /// </remarks>
+        public static void EnableBuffering(this HttpRequest request, int bufferThreshold, long bufferLimit)
+        {
+            BufferingHelper.EnableRewind(request, bufferThreshold, bufferLimit);
+        }
+    }
+}
diff --git a/src/Http/Http/src/Features/Authentication/HttpAuthenticationFeature.cs b/src/Http/Http/src/Features/Authentication/HttpAuthenticationFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9a14b657121ba467bfd693d7ff801f05269e9b84
--- /dev/null
+++ b/src/Http/Http/src/Features/Authentication/HttpAuthenticationFeature.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 System.Security.Claims;
+
+namespace Microsoft.AspNetCore.Http.Features.Authentication
+{
+    public class HttpAuthenticationFeature : IHttpAuthenticationFeature
+    {
+        public ClaimsPrincipal User
+        {
+            get;
+            set;
+        }
+
+        public IAuthenticationHandler Handler
+        {
+            get;
+            set;
+        }
+    }
+}
diff --git a/src/Http/Http/src/Features/DefaultSessionFeature.cs b/src/Http/Http/src/Features/DefaultSessionFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6790133467c8c59ee6eb16c64a319f9bf561ba03
--- /dev/null
+++ b/src/Http/Http/src/Features/DefaultSessionFeature.cs
@@ -0,0 +1,14 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    /// <summary>
+    /// This type exists only for the purpose of unit testing where the user can directly set the
+    /// <see cref="HttpContext.Session"/> property without the need for creating a <see cref="ISessionFeature"/>.
+    /// </summary>
+    public class DefaultSessionFeature : ISessionFeature
+    {
+        public ISession Session { get; set; }
+    }
+}
diff --git a/src/Http/Http/src/Features/FormFeature.cs b/src/Http/Http/src/Features/FormFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f091e3b16647e4f638cb9b8085a53aef508b682b
--- /dev/null
+++ b/src/Http/Http/src/Features/FormFeature.cs
@@ -0,0 +1,323 @@
+// 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;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Internal;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.Primitives;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class FormFeature : IFormFeature
+    {
+        private static readonly FormOptions DefaultFormOptions = new FormOptions();
+
+        private readonly HttpRequest _request;
+        private readonly FormOptions _options;
+        private Task<IFormCollection> _parsedFormTask;
+        private IFormCollection _form;
+
+        public FormFeature(IFormCollection form)
+        {
+            if (form == null)
+            {
+                throw new ArgumentNullException(nameof(form));
+            }
+
+            Form = form;
+        }
+        public FormFeature(HttpRequest request)
+            : this(request, DefaultFormOptions)
+        {
+        }
+
+        public FormFeature(HttpRequest request, FormOptions options)
+        {
+            if (request == null)
+            {
+                throw new ArgumentNullException(nameof(request));
+            }
+            if (options == null)
+            {
+                throw new ArgumentNullException(nameof(options));
+            }
+
+            _request = request;
+            _options = options;
+        }
+
+        private MediaTypeHeaderValue ContentType
+        {
+            get
+            {
+                MediaTypeHeaderValue mt;
+                MediaTypeHeaderValue.TryParse(_request.ContentType, out mt);
+                return mt;
+            }
+        }
+
+        public bool HasFormContentType
+        {
+            get
+            {
+                // Set directly
+                if (Form != null)
+                {
+                    return true;
+                }
+
+                var contentType = ContentType;
+                return HasApplicationFormContentType(contentType) || HasMultipartFormContentType(contentType);
+            }
+        }
+
+        public IFormCollection Form
+        {
+            get { return _form; }
+            set
+            {
+                _parsedFormTask = null;
+                _form = value;
+            }
+        }
+
+        public IFormCollection ReadForm()
+        {
+            if (Form != null)
+            {
+                return Form;
+            }
+
+            if (!HasFormContentType)
+            {
+                throw new InvalidOperationException("Incorrect Content-Type: " + _request.ContentType);
+            }
+
+            // TODO: Issue #456 Avoid Sync-over-Async http://blogs.msdn.com/b/pfxteam/archive/2012/04/13/10293638.aspx
+            // TODO: How do we prevent thread exhaustion?
+            return ReadFormAsync().GetAwaiter().GetResult();
+        }
+
+        public Task<IFormCollection> ReadFormAsync() => ReadFormAsync(CancellationToken.None);
+
+        public Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken)
+        {
+            // Avoid state machine and task allocation for repeated reads
+            if (_parsedFormTask == null)
+            {
+                if (Form != null)
+                {
+                    _parsedFormTask = Task.FromResult(Form);
+                }
+                else
+                {
+                    _parsedFormTask = InnerReadFormAsync(cancellationToken);
+                }
+            }
+            return _parsedFormTask;
+        }
+
+        private async Task<IFormCollection> InnerReadFormAsync(CancellationToken cancellationToken)
+        {
+            if (!HasFormContentType)
+            {
+                throw new InvalidOperationException("Incorrect Content-Type: " + _request.ContentType);
+            }
+
+            cancellationToken.ThrowIfCancellationRequested();
+
+            if (_options.BufferBody)
+            {
+                _request.EnableRewind(_options.MemoryBufferThreshold, _options.BufferBodyLengthLimit);
+            }
+
+            FormCollection formFields = null;
+            FormFileCollection files = null;
+
+            // Some of these code paths use StreamReader which does not support cancellation tokens.
+            using (cancellationToken.Register((state) => ((HttpContext)state).Abort(), _request.HttpContext))
+            {
+                var contentType = ContentType;
+                // Check the content-type
+                if (HasApplicationFormContentType(contentType))
+                {
+                    var encoding = FilterEncoding(contentType.Encoding);
+                    using (var formReader = new FormReader(_request.Body, encoding)
+                    {
+                        ValueCountLimit = _options.ValueCountLimit,
+                        KeyLengthLimit = _options.KeyLengthLimit,
+                        ValueLengthLimit = _options.ValueLengthLimit,
+                    })
+                    {
+                        formFields = new FormCollection(await formReader.ReadFormAsync(cancellationToken));
+                    }
+                }
+                else if (HasMultipartFormContentType(contentType))
+                {
+                    var formAccumulator = new KeyValueAccumulator();
+
+                    var boundary = GetBoundary(contentType, _options.MultipartBoundaryLengthLimit);
+                    var multipartReader = new MultipartReader(boundary, _request.Body)
+                    {
+                        HeadersCountLimit = _options.MultipartHeadersCountLimit,
+                        HeadersLengthLimit = _options.MultipartHeadersLengthLimit,
+                        BodyLengthLimit = _options.MultipartBodyLengthLimit,
+                    };
+                    var section = await multipartReader.ReadNextSectionAsync(cancellationToken);
+                    while (section != null)
+                    {
+                        // Parse the content disposition here and pass it further to avoid reparsings
+                        ContentDispositionHeaderValue contentDisposition;
+                        ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out contentDisposition);
+
+                        if (contentDisposition.IsFileDisposition())
+                        {
+                            var fileSection = new FileMultipartSection(section, contentDisposition);
+
+                            // Enable buffering for the file if not already done for the full body
+                            section.EnableRewind(
+                                _request.HttpContext.Response.RegisterForDispose,
+                                _options.MemoryBufferThreshold, _options.MultipartBodyLengthLimit);
+
+                            // Find the end
+                            await section.Body.DrainAsync(cancellationToken);
+
+                            var name = fileSection.Name;
+                            var fileName = fileSection.FileName;
+
+                            FormFile file;
+                            if (section.BaseStreamOffset.HasValue)
+                            {
+                                // Relative reference to buffered request body
+                                file = new FormFile(_request.Body, section.BaseStreamOffset.Value, section.Body.Length, name, fileName);
+                            }
+                            else
+                            {
+                                // Individually buffered file body
+                                file = new FormFile(section.Body, 0, section.Body.Length, name, fileName);
+                            }
+                            file.Headers = new HeaderDictionary(section.Headers);
+
+                            if (files == null)
+                            {
+                                files = new FormFileCollection();
+                            }
+                            if (files.Count >= _options.ValueCountLimit)
+                            {
+                                throw new InvalidDataException($"Form value count limit {_options.ValueCountLimit} exceeded.");
+                            }
+                            files.Add(file);
+                        }
+                        else if (contentDisposition.IsFormDisposition())
+                        {
+                            var formDataSection = new FormMultipartSection(section, contentDisposition);
+
+                            // Content-Disposition: form-data; name="key"
+                            //
+                            // value
+
+                            // Do not limit the key name length here because the mulipart headers length limit is already in effect.
+                            var key = formDataSection.Name;
+                            var value = await formDataSection.GetValueAsync();
+
+                            formAccumulator.Append(key, value);
+                            if (formAccumulator.ValueCount > _options.ValueCountLimit)
+                            {
+                                throw new InvalidDataException($"Form value count limit {_options.ValueCountLimit} exceeded.");
+                            }
+                        }
+                        else
+                        {
+                            System.Diagnostics.Debug.Assert(false, "Unrecognized content-disposition for this section: " + section.ContentDisposition);
+                        }
+
+                        section = await multipartReader.ReadNextSectionAsync(cancellationToken);
+                    }
+
+                    if (formAccumulator.HasValues)
+                    {
+                        formFields = new FormCollection(formAccumulator.GetResults(), files);
+                    }
+                }
+            }
+
+            // Rewind so later readers don't have to.
+            if (_request.Body.CanSeek)
+            {
+                _request.Body.Seek(0, SeekOrigin.Begin);
+            }
+
+            if (formFields != null)
+            {
+                Form = formFields;
+            }
+            else if (files != null)
+            {
+                Form = new FormCollection(null, files);
+            }
+            else
+            {
+                Form = FormCollection.Empty;
+            }
+
+            return Form;
+        }
+
+        private Encoding FilterEncoding(Encoding encoding)
+        {
+            // UTF-7 is insecure and should not be honored. UTF-8 will succeed for most cases.
+            if (encoding == null || Encoding.UTF7.Equals(encoding))
+            {
+                return Encoding.UTF8;
+            }
+            return encoding;
+        }
+
+        private bool HasApplicationFormContentType(MediaTypeHeaderValue contentType)
+        {
+            // Content-Type: application/x-www-form-urlencoded; charset=utf-8
+            return contentType != null && contentType.MediaType.Equals("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase);
+        }
+
+        private bool HasMultipartFormContentType(MediaTypeHeaderValue contentType)
+        {
+            // Content-Type: multipart/form-data; boundary=----WebKitFormBoundarymx2fSWqWSd0OxQqq
+            return contentType != null && contentType.MediaType.Equals("multipart/form-data", StringComparison.OrdinalIgnoreCase);
+        }
+
+        private bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition)
+        {
+            // Content-Disposition: form-data; name="key";
+            return contentDisposition != null && contentDisposition.DispositionType.Equals("form-data")
+                && StringSegment.IsNullOrEmpty(contentDisposition.FileName) && StringSegment.IsNullOrEmpty(contentDisposition.FileNameStar);
+        }
+
+        private bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition)
+        {
+            // Content-Disposition: form-data; name="myfile1"; filename="Misc 002.jpg"
+            return contentDisposition != null && contentDisposition.DispositionType.Equals("form-data")
+                && (!StringSegment.IsNullOrEmpty(contentDisposition.FileName) || !StringSegment.IsNullOrEmpty(contentDisposition.FileNameStar));
+        }
+
+        // Content-Type: multipart/form-data; boundary="----WebKitFormBoundarymx2fSWqWSd0OxQqq"
+        // The spec says 70 characters is a reasonable limit.
+        private static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
+        {
+            var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary);
+            if (StringSegment.IsNullOrEmpty(boundary))
+            {
+                throw new InvalidDataException("Missing content-type boundary.");
+            }
+            if (boundary.Length > lengthLimit)
+            {
+                throw new InvalidDataException($"Multipart boundary length limit {lengthLimit} exceeded.");
+            }
+            return boundary.ToString();
+        }
+    }
+}
diff --git a/src/Http/Http/src/Features/FormOptions.cs b/src/Http/Http/src/Features/FormOptions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..17e521b215748e24d64aff22c66e32dac8ce48ed
--- /dev/null
+++ b/src/Http/Http/src/Features/FormOptions.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.IO;
+using Microsoft.AspNetCore.WebUtilities;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class FormOptions
+    {
+        public const int DefaultMemoryBufferThreshold = 1024 * 64;
+        public const int DefaultBufferBodyLengthLimit = 1024 * 1024 * 128;
+        public const int DefaultMultipartBoundaryLengthLimit = 128;
+        public const long DefaultMultipartBodyLengthLimit = 1024 * 1024 * 128;
+
+        /// <summary>
+        /// Enables full request body buffering. Use this if multiple components need to read the raw stream.
+        /// The default value is false.
+        /// </summary>
+        public bool BufferBody { get; set; } = false;
+
+        /// <summary>
+        /// If <see cref="BufferBody"/> is enabled, this many bytes of the body will be buffered in memory.
+        /// If this threshold is exceeded then the buffer will be moved to a temp file on disk instead.
+        /// This also applies when buffering individual multipart section bodies.
+        /// </summary>
+        public int MemoryBufferThreshold { get; set; } = DefaultMemoryBufferThreshold;
+
+        /// <summary>
+        /// If <see cref="BufferBody"/> is enabled, this is the limit for the total number of bytes that will
+        /// be buffered. Forms that exceed this limit will throw an <see cref="InvalidDataException"/> when parsed.
+        /// </summary>
+        public long BufferBodyLengthLimit { get; set; } = DefaultBufferBodyLengthLimit;
+
+        /// <summary>
+        /// A limit for the number of form entries to allow.
+        /// Forms that exceed this limit will throw an <see cref="InvalidDataException"/> when parsed.
+        /// </summary>
+        public int ValueCountLimit { get; set; } = FormReader.DefaultValueCountLimit;
+
+        /// <summary>
+        /// A limit on the length of individual keys. Forms containing keys that exceed this limit will
+        /// throw an <see cref="InvalidDataException"/> when parsed.
+        /// </summary>
+        public int KeyLengthLimit { get; set; } = FormReader.DefaultKeyLengthLimit;
+
+        /// <summary>
+        /// A limit on the length of individual form values. Forms containing values that exceed this
+        /// limit will throw an <see cref="InvalidDataException"/> when parsed.
+        /// </summary>
+        public int ValueLengthLimit { get; set; } = FormReader.DefaultValueLengthLimit;
+
+        /// <summary>
+        /// A limit for the length of the boundary identifier. Forms with boundaries that exceed this
+        /// limit will throw an <see cref="InvalidDataException"/> when parsed.
+        /// </summary>
+        public int MultipartBoundaryLengthLimit { get; set; } = DefaultMultipartBoundaryLengthLimit;
+
+        /// <summary>
+        /// A limit for the number of headers to allow in each multipart section. Headers with the same name will
+        /// be combined. Form sections that exceed this limit will throw an <see cref="InvalidDataException"/>
+        /// when parsed.
+        /// </summary>
+        public int MultipartHeadersCountLimit { get; set; } = MultipartReader.DefaultHeadersCountLimit;
+
+        /// <summary>
+        /// A limit for the total length of the header keys and values in each multipart section.
+        /// Form sections that exceed this limit will throw an <see cref="InvalidDataException"/> when parsed.
+        /// </summary>
+        public int MultipartHeadersLengthLimit { get; set; } = MultipartReader.DefaultHeadersLengthLimit;
+
+        /// <summary>
+        /// A limit for the length of each multipart body. Forms sections that exceed this limit will throw an
+        /// <see cref="InvalidDataException"/> when parsed.
+        /// </summary>
+        public long MultipartBodyLengthLimit { get; set; } = DefaultMultipartBodyLengthLimit;
+    }
+}
diff --git a/src/Http/Http/src/Features/HttpConnectionFeature.cs b/src/Http/Http/src/Features/HttpConnectionFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2e8d5b0a1c6db761b27e69b9ad2c5a49bc6d3f60
--- /dev/null
+++ b/src/Http/Http/src/Features/HttpConnectionFeature.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 System.Net;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class HttpConnectionFeature : IHttpConnectionFeature
+    {
+        public string ConnectionId { get; set; }
+
+        public IPAddress LocalIpAddress { get; set; }
+
+        public int LocalPort { get; set; }
+
+        public IPAddress RemoteIpAddress { get; set; }
+
+        public int RemotePort { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/Features/HttpRequestFeature.cs b/src/Http/Http/src/Features/HttpRequestFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b8b667bf4e43e7a644d3117f30309e876699df81
--- /dev/null
+++ b/src/Http/Http/src/Features/HttpRequestFeature.cs
@@ -0,0 +1,33 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class HttpRequestFeature : IHttpRequestFeature
+    {
+        public HttpRequestFeature()
+        {
+            Headers = new HeaderDictionary();
+            Body = Stream.Null;
+            Protocol = string.Empty;
+            Scheme = string.Empty;
+            Method = string.Empty;
+            PathBase = string.Empty;
+            Path = string.Empty;
+            QueryString = string.Empty;
+            RawTarget = string.Empty;
+        }
+
+        public string Protocol { get; set; }
+        public string Scheme { get; set; }
+        public string Method { get; set; }
+        public string PathBase { get; set; }
+        public string Path { get; set; }
+        public string QueryString { get; set; }
+        public string RawTarget { get; set; }
+        public IHeaderDictionary Headers { get; set; }
+        public Stream Body { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/Features/HttpRequestIdentifierFeature.cs b/src/Http/Http/src/Features/HttpRequestIdentifierFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..34663937a5a5ba09b9d0c661d043d58382dc4c7a
--- /dev/null
+++ b/src/Http/Http/src/Features/HttpRequestIdentifierFeature.cs
@@ -0,0 +1,64 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class HttpRequestIdentifierFeature : IHttpRequestIdentifierFeature
+    {
+        // Base32 encoding - in ascii sort order for easy text based sorting 
+        private static readonly string _encode32Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
+        // Seed the _requestId for this application instance with 
+        // the number of 100-nanosecond intervals that have elapsed since 12:00:00 midnight, January 1, 0001
+        // for a roughly increasing _requestId over restarts
+        private static long _requestId = DateTime.UtcNow.Ticks;
+
+        private string _id = null;
+
+        public string TraceIdentifier
+        {
+            get
+            {
+                // Don't incur the cost of generating the request ID until it's asked for
+                if (_id == null)
+                {
+                    _id = GenerateRequestId(Interlocked.Increment(ref _requestId));
+                }
+                return _id;
+            }
+            set
+            {
+                _id = value;
+            }
+        }
+
+        private static unsafe string GenerateRequestId(long id)
+        {
+            // The following routine is ~310% faster than calling long.ToString() on x64
+            // and ~600% faster than calling long.ToString() on x86 in tight loops of 1 million+ iterations
+            // See: https://github.com/aspnet/Hosting/pull/385
+
+            // stackalloc to allocate array on stack rather than heap
+            char* charBuffer = stackalloc char[13];
+
+            charBuffer[0] = _encode32Chars[(int)(id >> 60) & 31];
+            charBuffer[1] = _encode32Chars[(int)(id >> 55) & 31];
+            charBuffer[2] = _encode32Chars[(int)(id >> 50) & 31];
+            charBuffer[3] = _encode32Chars[(int)(id >> 45) & 31];
+            charBuffer[4] = _encode32Chars[(int)(id >> 40) & 31];
+            charBuffer[5] = _encode32Chars[(int)(id >> 35) & 31];
+            charBuffer[6] = _encode32Chars[(int)(id >> 30) & 31];
+            charBuffer[7] = _encode32Chars[(int)(id >> 25) & 31];
+            charBuffer[8] = _encode32Chars[(int)(id >> 20) & 31];
+            charBuffer[9] = _encode32Chars[(int)(id >> 15) & 31];
+            charBuffer[10] = _encode32Chars[(int)(id >> 10) & 31];
+            charBuffer[11] = _encode32Chars[(int)(id >> 5) & 31];
+            charBuffer[12] = _encode32Chars[(int)id & 31];
+
+            // string ctor overload that takes char*
+            return new string(charBuffer, 0, 13);
+        }
+    }
+}
diff --git a/src/Http/Http/src/Features/HttpRequestLifetimeFeature.cs b/src/Http/Http/src/Features/HttpRequestLifetimeFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..df327d07582112c8d2fc2aa791f47b8863746d35
--- /dev/null
+++ b/src/Http/Http/src/Features/HttpRequestLifetimeFeature.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class HttpRequestLifetimeFeature : IHttpRequestLifetimeFeature
+    {
+        public CancellationToken RequestAborted { get; set; }
+
+        public void Abort()
+        {
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/Features/HttpResponseFeature.cs b/src/Http/Http/src/Features/HttpResponseFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a02a79088bcd5831e96788a7d53d222bf112e764
--- /dev/null
+++ b/src/Http/Http/src/Features/HttpResponseFeature.cs
@@ -0,0 +1,40 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class HttpResponseFeature : IHttpResponseFeature
+    {
+        public HttpResponseFeature()
+        {
+            StatusCode = 200;
+            Headers = new HeaderDictionary();
+            Body = Stream.Null;
+        }
+
+        public int StatusCode { get; set; }
+
+        public string ReasonPhrase { get; set; }
+
+        public IHeaderDictionary Headers { get; set; }
+
+        public Stream Body { get; set; }
+
+        public virtual bool HasStarted
+        {
+            get { return false; }
+        }
+
+        public virtual void OnStarting(Func<object, Task> callback, object state)
+        {
+        }
+
+        public virtual void OnCompleted(Func<object, Task> callback, object state)
+        {
+        }
+    }
+}
diff --git a/src/Http/Http/src/Features/ItemsFeature.cs b/src/Http/Http/src/Features/ItemsFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6bf0669b45c121857781f57815c0eae48cd82315
--- /dev/null
+++ b/src/Http/Http/src/Features/ItemsFeature.cs
@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Http.Internal;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class ItemsFeature : IItemsFeature
+    {
+        public ItemsFeature()
+        {
+            Items = new ItemsDictionary();
+        }
+
+        public IDictionary<object, object> Items { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/Features/QueryFeature.cs b/src/Http/Http/src/Features/QueryFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..36781ef16eba8834d2302dc60272cb1db68743f9
--- /dev/null
+++ b/src/Http/Http/src/Features/QueryFeature.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 Microsoft.AspNetCore.Http.Internal;
+using Microsoft.AspNetCore.WebUtilities;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class QueryFeature : IQueryFeature
+    {
+        // Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624
+        private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;
+
+        private FeatureReferences<IHttpRequestFeature> _features;
+
+        private string _original;
+        private IQueryCollection _parsedValues;
+
+        public QueryFeature(IQueryCollection query)
+        {
+            if (query == null)
+            {
+                throw new ArgumentNullException(nameof(query));
+            }
+
+            _parsedValues = query;
+        }
+
+        public QueryFeature(IFeatureCollection features)
+        {
+            if (features == null)
+            {
+                throw new ArgumentNullException(nameof(features));
+            }
+
+            _features = new FeatureReferences<IHttpRequestFeature>(features);
+        }
+
+        private IHttpRequestFeature HttpRequestFeature =>
+            _features.Fetch(ref _features.Cache, _nullRequestFeature);
+
+        public IQueryCollection Query
+        {
+            get
+            {
+                if (_features.Collection == null)
+                {
+                    if (_parsedValues == null)
+                    {
+                        _parsedValues = QueryCollection.Empty;
+                    }
+                    return _parsedValues;
+                }
+
+                var current = HttpRequestFeature.QueryString;
+                if (_parsedValues == null || !string.Equals(_original, current, StringComparison.Ordinal))
+                {
+                    _original = current;
+
+                    var result = QueryHelpers.ParseNullableQuery(current);
+
+                    if (result == null)
+                    {
+                        _parsedValues = QueryCollection.Empty;
+                    }
+                    else
+                    {
+                        _parsedValues = new QueryCollection(result);
+                    }
+                }
+                return _parsedValues;
+            }
+            set
+            {
+                _parsedValues = value;
+                if (_features.Collection != null)
+                {
+                    if (value == null)
+                    {
+                        _original = string.Empty;
+                        HttpRequestFeature.QueryString = string.Empty;
+                    }
+                    else
+                    {
+                        _original = QueryString.Create(_parsedValues).ToString();
+                        HttpRequestFeature.QueryString = _original;
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/Features/RequestCookiesFeature.cs b/src/Http/Http/src/Features/RequestCookiesFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..cd37b360a4efcad046600a0d22f99f0a269a0340
--- /dev/null
+++ b/src/Http/Http/src/Features/RequestCookiesFeature.cs
@@ -0,0 +1,96 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Http.Internal;
+using Microsoft.Extensions.Primitives;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class RequestCookiesFeature : IRequestCookiesFeature
+    {
+        // Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624
+        private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;
+
+        private FeatureReferences<IHttpRequestFeature> _features;
+        private StringValues _original;
+        private IRequestCookieCollection _parsedValues;
+
+        public RequestCookiesFeature(IRequestCookieCollection cookies)
+        {
+            if (cookies == null)
+            {
+                throw new ArgumentNullException(nameof(cookies));
+            }
+
+            _parsedValues = cookies;
+        }
+
+        public RequestCookiesFeature(IFeatureCollection features)
+        {
+            if (features == null)
+            {
+                throw new ArgumentNullException(nameof(features));
+            }
+
+            _features = new FeatureReferences<IHttpRequestFeature>(features);
+        }
+
+        private IHttpRequestFeature HttpRequestFeature =>
+            _features.Fetch(ref _features.Cache, _nullRequestFeature);
+
+        public IRequestCookieCollection Cookies
+        {
+            get
+            {
+                if (_features.Collection == null)
+                {
+                    if (_parsedValues == null)
+                    {
+                        _parsedValues = RequestCookieCollection.Empty;
+                    }
+                    return _parsedValues;
+                }
+
+                var headers = HttpRequestFeature.Headers;
+                StringValues current;
+                if (!headers.TryGetValue(HeaderNames.Cookie, out current))
+                {
+                    current = string.Empty;
+                }
+
+                if (_parsedValues == null || _original != current)
+                {
+                    _original = current;
+                    _parsedValues = RequestCookieCollection.Parse(current.ToArray());
+                }
+
+                return _parsedValues;
+            }
+            set
+            {
+                _parsedValues = value;
+                _original = StringValues.Empty;
+                if (_features.Collection != null)
+                {
+                    if (_parsedValues == null || _parsedValues.Count == 0)
+                    {
+                        HttpRequestFeature.Headers.Remove(HeaderNames.Cookie);
+                    }
+                    else
+                    {
+                        var headers = new List<string>();
+                        foreach (var pair in _parsedValues)
+                        {
+                            headers.Add(new CookieHeaderValue(pair.Key, pair.Value).ToString());
+                        }
+                        _original = headers.ToArray();
+                        HttpRequestFeature.Headers[HeaderNames.Cookie] = _original;
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/Features/ResponseCookiesFeature.cs b/src/Http/Http/src/Features/ResponseCookiesFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0d9444b0f5626559aa64a1de54f3d4b9e3e399cd
--- /dev/null
+++ b/src/Http/Http/src/Features/ResponseCookiesFeature.cs
@@ -0,0 +1,69 @@
+// 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.Text;
+using Microsoft.AspNetCore.Http.Internal;
+using Microsoft.Extensions.ObjectPool;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    /// <summary>
+    /// Default implementation of <see cref="IResponseCookiesFeature"/>.
+    /// </summary>
+    public class ResponseCookiesFeature : IResponseCookiesFeature
+    {
+        // Lambda hoisted to static readonly field to improve inlining https://github.com/dotnet/roslyn/issues/13624
+        private readonly static Func<IFeatureCollection, IHttpResponseFeature> _nullResponseFeature = f => null;
+
+        private FeatureReferences<IHttpResponseFeature> _features;
+        private IResponseCookies _cookiesCollection;
+
+        /// <summary>
+        /// Initializes a new <see cref="ResponseCookiesFeature"/> instance.
+        /// </summary>
+        /// <param name="features">
+        /// <see cref="IFeatureCollection"/> containing all defined features, including this
+        /// <see cref="IResponseCookiesFeature"/> and the <see cref="IHttpResponseFeature"/>.
+        /// </param>
+        public ResponseCookiesFeature(IFeatureCollection features)
+            : this(features, builderPool: null)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new <see cref="ResponseCookiesFeature"/> instance.
+        /// </summary>
+        /// <param name="features">
+        /// <see cref="IFeatureCollection"/> containing all defined features, including this
+        /// <see cref="IResponseCookiesFeature"/> and the <see cref="IHttpResponseFeature"/>.
+        /// </param>
+        /// <param name="builderPool">The <see cref="ObjectPool{T}"/>, if available.</param>
+        public ResponseCookiesFeature(IFeatureCollection features, ObjectPool<StringBuilder> builderPool)
+        {
+            if (features == null)
+            {
+                throw new ArgumentNullException(nameof(features));
+            }
+
+            _features = new FeatureReferences<IHttpResponseFeature>(features);
+        }
+
+        private IHttpResponseFeature HttpResponseFeature => _features.Fetch(ref _features.Cache, _nullResponseFeature);
+
+        /// <inheritdoc />
+        public IResponseCookies Cookies
+        {
+            get
+            {
+                if (_cookiesCollection == null)
+                {
+                    var headers = HttpResponseFeature.Headers;
+                    _cookiesCollection = new ResponseCookies(headers, null);
+                }
+
+                return _cookiesCollection;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/Features/ServiceProvidersFeature.cs b/src/Http/Http/src/Features/ServiceProvidersFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d1cf4e6cba83a2eef3e48292d2564e55f13c26d9
--- /dev/null
+++ b/src/Http/Http/src/Features/ServiceProvidersFeature.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.
+
+using System;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class ServiceProvidersFeature : IServiceProvidersFeature
+    {
+        public IServiceProvider RequestServices { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/Features/TlsConnectionFeature.cs b/src/Http/Http/src/Features/TlsConnectionFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f9bfcdef7feebf5fc28925979708f307cf7363ab
--- /dev/null
+++ b/src/Http/Http/src/Features/TlsConnectionFeature.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.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class TlsConnectionFeature : ITlsConnectionFeature
+    {
+        public X509Certificate2 ClientCertificate { get; set; }
+
+        public Task<X509Certificate2> GetClientCertificateAsync(CancellationToken cancellationToken)
+        {
+            return Task.FromResult(ClientCertificate);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/FormCollection.cs b/src/Http/Http/src/FormCollection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..23709b2bb064ed74634bad14bfb78cdf3ef721b4
--- /dev/null
+++ b/src/Http/Http/src/FormCollection.cs
@@ -0,0 +1,228 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Http.Internal;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Contains the parsed form values.
+    /// </summary>
+    public class FormCollection : IFormCollection
+    {
+        public static readonly FormCollection Empty = new FormCollection();
+        private static readonly string[] EmptyKeys = Array.Empty<string>();
+        private static readonly StringValues[] EmptyValues = Array.Empty<StringValues>();
+        private static readonly Enumerator EmptyEnumerator = new Enumerator();
+        // Pre-box
+        private static readonly IEnumerator<KeyValuePair<string, StringValues>> EmptyIEnumeratorType = EmptyEnumerator;
+        private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator;
+
+        private static IFormFileCollection EmptyFiles = new FormFileCollection();
+
+        private IFormFileCollection _files;
+
+        private FormCollection()
+        {
+            // For static Empty
+        }
+
+        public FormCollection(Dictionary<string, StringValues> fields, IFormFileCollection files = null)
+        {
+            // can be null
+            Store = fields;
+            _files = files;
+        }
+
+        public IFormFileCollection Files
+        {
+            get
+            {
+                return _files ?? EmptyFiles;
+            }
+            private set { _files = value; }
+        }
+
+        private Dictionary<string, StringValues> Store { get; set; }
+
+        /// <summary>
+        /// Get or sets the associated value from the collection as a single string.
+        /// </summary>
+        /// <param name="key">The header name.</param>
+        /// <returns>the associated value from the collection as a StringValues or StringValues.Empty if the key is not present.</returns>
+        public StringValues this[string key]
+        {
+            get
+            {
+                if (Store == null)
+                {
+                    return StringValues.Empty;
+                }
+
+                StringValues value;
+                if (TryGetValue(key, out value))
+                {
+                    return value;
+                }
+                return StringValues.Empty;
+            }
+        }
+
+        /// <summary>
+        /// Gets the number of elements contained in the <see cref="HeaderDictionary" />;.
+        /// </summary>
+        /// <returns>The number of elements contained in the <see cref="HeaderDictionary" />.</returns>
+        public int Count
+        {
+            get
+            {
+                return Store?.Count ?? 0;
+            }
+        }
+
+        public ICollection<string> Keys
+        {
+            get
+            {
+                if (Store == null)
+                {
+                    return EmptyKeys;
+                }
+                return Store.Keys;
+            }
+        }
+
+        /// <summary>
+        /// Determines whether the <see cref="HeaderDictionary" /> contains a specific key.
+        /// </summary>
+        /// <param name="key">The key.</param>
+        /// <returns>true if the <see cref="HeaderDictionary" /> contains a specific key; otherwise, false.</returns>
+        public bool ContainsKey(string key)
+        {
+            if (Store == null)
+            {
+                return false;
+            }
+            return Store.ContainsKey(key);
+        }
+
+        /// <summary>
+        /// Retrieves a value from the dictionary.
+        /// </summary>
+        /// <param name="key">The header name.</param>
+        /// <param name="value">The value.</param>
+        /// <returns>true if the <see cref="HeaderDictionary" /> contains the key; otherwise, false.</returns>
+        public bool TryGetValue(string key, out StringValues value)
+        {
+            if (Store == null)
+            {
+                value = default(StringValues);
+                return false;
+            }
+            return Store.TryGetValue(key, out value);
+        }
+
+        /// <summary>
+        /// Returns an struct enumerator that iterates through a collection without boxing and is also used via the <see cref="IFormCollection" /> interface.
+        /// </summary>
+        /// <returns>An <see cref="Enumerator" /> object that can be used to iterate through the collection.</returns>
+        public Enumerator GetEnumerator()
+        {
+            if (Store == null || Store.Count == 0)
+            {
+                // Non-boxed Enumerator
+                return EmptyEnumerator;
+            }
+            // Non-boxed Enumerator
+            return new Enumerator(Store.GetEnumerator());
+        }
+
+        /// <summary>
+        /// Returns an enumerator that iterates through a collection, boxes in non-empty path.
+        /// </summary>
+        /// <returns>An <see cref="IEnumerator" /> object that can be used to iterate through the collection.</returns>
+        IEnumerator<KeyValuePair<string, StringValues>> IEnumerable<KeyValuePair<string, StringValues>>.GetEnumerator()
+        {
+            if (Store == null || Store.Count == 0)
+            {
+                // Non-boxed Enumerator
+                return EmptyIEnumeratorType;
+            }
+            // Boxed Enumerator
+            return Store.GetEnumerator();
+        }
+
+        /// <summary>
+        /// Returns an enumerator that iterates through a collection, boxes in non-empty path.
+        /// </summary>
+        /// <returns>An <see cref="IEnumerator" /> object that can be used to iterate through the collection.</returns>
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            if (Store == null || Store.Count == 0)
+            {
+                // Non-boxed Enumerator
+                return EmptyIEnumerator;
+            }
+            // Boxed Enumerator
+            return Store.GetEnumerator();
+        }
+
+        public struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
+        {
+            // Do NOT make this readonly, or MoveNext will not work
+            private Dictionary<string, StringValues>.Enumerator _dictionaryEnumerator;
+            private bool _notEmpty;
+
+            internal Enumerator(Dictionary<string, StringValues>.Enumerator dictionaryEnumerator)
+            {
+                _dictionaryEnumerator = dictionaryEnumerator;
+                _notEmpty = true;
+            }
+
+            public bool MoveNext()
+            {
+                if (_notEmpty)
+                {
+                    return _dictionaryEnumerator.MoveNext();
+                }
+                return false;
+            }
+
+            public KeyValuePair<string, StringValues> Current
+            {
+                get
+                {
+                    if (_notEmpty)
+                    {
+                        return _dictionaryEnumerator.Current;
+                    }
+                    return default(KeyValuePair<string, StringValues>);
+                }
+            }
+
+            public void Dispose()
+            {
+            }
+
+            object IEnumerator.Current
+            {
+                get
+                {
+                    return Current;
+                }
+            }
+
+            void IEnumerator.Reset()
+            {
+                if (_notEmpty)
+                {
+                    ((IEnumerator)_dictionaryEnumerator).Reset();
+                }
+            }
+        }
+    }
+}
diff --git a/src/Http/Http/src/HeaderDictionary.cs b/src/Http/Http/src/HeaderDictionary.cs
new file mode 100644
index 0000000000000000000000000000000000000000..bc0b7a26ce32baf175897d0cdbeaf847858782ee
--- /dev/null
+++ b/src/Http/Http/src/HeaderDictionary.cs
@@ -0,0 +1,416 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Microsoft.Extensions.Primitives;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http
+{
+    /// <summary>
+    /// Represents a wrapper for RequestHeaders and ResponseHeaders.
+    /// </summary>
+    public class HeaderDictionary : IHeaderDictionary
+    {
+        private static readonly string[] EmptyKeys = Array.Empty<string>();
+        private static readonly StringValues[] EmptyValues = Array.Empty<StringValues>();
+        private static readonly Enumerator EmptyEnumerator = new Enumerator();
+        // Pre-box
+        private static readonly IEnumerator<KeyValuePair<string, StringValues>> EmptyIEnumeratorType = EmptyEnumerator;
+        private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator;
+
+        public HeaderDictionary()
+        {
+        }
+
+        public HeaderDictionary(Dictionary<string, StringValues> store)
+        {
+            Store = store;
+        }
+
+        public HeaderDictionary(int capacity)
+        {
+            EnsureStore(capacity);
+        }
+
+        private Dictionary<string, StringValues> Store { get; set; }
+
+        private void EnsureStore(int capacity)
+        {
+            if (Store == null)
+            {
+                Store = new Dictionary<string, StringValues>(capacity, StringComparer.OrdinalIgnoreCase);
+            }
+        }
+
+        /// <summary>
+        /// Get or sets the associated value from the collection as a single string.
+        /// </summary>
+        /// <param name="key">The header name.</param>
+        /// <returns>the associated value from the collection as a StringValues or StringValues.Empty if the key is not present.</returns>
+        public StringValues this[string key]
+        {
+            get
+            {
+                if (Store == null)
+                {
+                    return StringValues.Empty;
+                }
+
+                StringValues value;
+                if (TryGetValue(key, out value))
+                {
+                    return value;
+                }
+                return StringValues.Empty;
+            }
+            set
+            {
+                if (key == null)
+                {
+                    throw new ArgumentNullException(nameof(key));
+                }
+                ThrowIfReadOnly();
+
+                if (StringValues.IsNullOrEmpty(value))
+                {
+                    Store?.Remove(key);
+                }
+                else
+                {
+                    EnsureStore(1);
+                    Store[key] = value;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Throws KeyNotFoundException if the key is not present.
+        /// </summary>
+        /// <param name="key">The header name.</param>
+        /// <returns></returns>
+        StringValues IDictionary<string, StringValues>.this[string key]
+        {
+            get { return Store[key]; }
+            set
+            {
+                ThrowIfReadOnly();
+                this[key] = value;
+            }
+        }
+
+        public long? ContentLength
+        {
+            get
+            {
+                long value;
+                var rawValue = this[HeaderNames.ContentLength];
+                if (rawValue.Count == 1 &&
+                    !string.IsNullOrEmpty(rawValue[0]) &&
+                    HeaderUtilities.TryParseNonNegativeInt64(new StringSegment(rawValue[0]).Trim(), out value))
+                {
+                    return value;
+                }
+
+                return null;
+            }
+            set
+            {
+                ThrowIfReadOnly();
+                if (value.HasValue)
+                {
+                    this[HeaderNames.ContentLength] = HeaderUtilities.FormatNonNegativeInt64(value.Value);
+                }
+                else
+                {
+                    this.Remove(HeaderNames.ContentLength);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets the number of elements contained in the <see cref="HeaderDictionary" />;.
+        /// </summary>
+        /// <returns>The number of elements contained in the <see cref="HeaderDictionary" />.</returns>
+        public int Count => Store?.Count ?? 0;
+
+        /// <summary>
+        /// Gets a value that indicates whether the <see cref="HeaderDictionary" /> is in read-only mode.
+        /// </summary>
+        /// <returns>true if the <see cref="HeaderDictionary" /> is in read-only mode; otherwise, false.</returns>
+        public bool IsReadOnly { get; set; }
+
+        public ICollection<string> Keys
+        {
+            get
+            {
+                if (Store == null)
+                {
+                    return EmptyKeys;
+                }
+                return Store.Keys;
+            }
+        }
+
+        public ICollection<StringValues> Values
+        {
+            get
+            {
+                if (Store == null)
+                {
+                    return EmptyValues;
+                }
+                return Store.Values;
+            }
+        }
+
+        /// <summary>
+        /// Adds a new list of items to the collection.
+        /// </summary>
+        /// <param name="item">The item to add.</param>
+        public void Add(KeyValuePair<string, StringValues> item)
+        {
+            if (item.Key == null)
+            {
+                throw new ArgumentNullException("The key is null");
+            }
+            ThrowIfReadOnly();
+            EnsureStore(1);
+            Store.Add(item.Key, item.Value);
+        }
+
+        /// <summary>
+        /// Adds the given header and values to the collection.
+        /// </summary>
+        /// <param name="key">The header name.</param>
+        /// <param name="value">The header values.</param>
+        public void Add(string key, StringValues value)
+        {
+            if (key == null)
+            {
+                throw new ArgumentNullException(nameof(key));
+            }
+            ThrowIfReadOnly();
+            EnsureStore(1);
+            Store.Add(key, value);
+        }
+
+        /// <summary>
+        /// Clears the entire list of objects.
+        /// </summary>
+        public void Clear()
+        {
+            ThrowIfReadOnly();
+            Store?.Clear();
+        }
+
+        /// <summary>
+        /// Returns a value indicating whether the specified object occurs within this collection.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns>true if the specified object occurs within this collection; otherwise, false.</returns>
+        public bool Contains(KeyValuePair<string, StringValues> item)
+        {
+            StringValues value;
+            if (Store == null ||
+                !Store.TryGetValue(item.Key, out value) ||
+                !StringValues.Equals(value, item.Value))
+            {
+                return false;
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// Determines whether the <see cref="HeaderDictionary" /> contains a specific key.
+        /// </summary>
+        /// <param name="key">The key.</param>
+        /// <returns>true if the <see cref="HeaderDictionary" /> contains a specific key; otherwise, false.</returns>
+        public bool ContainsKey(string key)
+        {
+            if (Store == null)
+            {
+                return false;
+            }
+            return Store.ContainsKey(key);
+        }
+
+        /// <summary>
+        /// Copies the <see cref="HeaderDictionary" /> elements to a one-dimensional Array instance at the specified index.
+        /// </summary>
+        /// <param name="array">The one-dimensional Array that is the destination of the specified objects copied from the <see cref="HeaderDictionary" />.</param>
+        /// <param name="arrayIndex">The zero-based index in <paramref name="array" /> at which copying begins.</param>
+        public void CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex)
+        {
+            if (Store == null)
+            {
+                return;
+            }
+
+            foreach (var item in Store)
+            {
+                array[arrayIndex] = item;
+                arrayIndex++;
+            }
+        }
+
+        /// <summary>
+        /// Removes the given item from the the collection.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns>true if the specified object was removed from the collection; otherwise, false.</returns>
+        public bool Remove(KeyValuePair<string, StringValues> item)
+        {
+            ThrowIfReadOnly();
+            if (Store == null)
+            {
+                return false;
+            }
+
+            StringValues value;
+
+            if (Store.TryGetValue(item.Key, out value) && StringValues.Equals(item.Value, value))
+            {
+                return Store.Remove(item.Key);
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Removes the given header from the collection.
+        /// </summary>
+        /// <param name="key">The header name.</param>
+        /// <returns>true if the specified object was removed from the collection; otherwise, false.</returns>
+        public bool Remove(string key)
+        {
+            ThrowIfReadOnly();
+            if (Store == null)
+            {
+                return false;
+            }
+            return Store.Remove(key);
+        }
+
+        /// <summary>
+        /// Retrieves a value from the dictionary.
+        /// </summary>
+        /// <param name="key">The header name.</param>
+        /// <param name="value">The value.</param>
+        /// <returns>true if the <see cref="HeaderDictionary" /> contains the key; otherwise, false.</returns>
+        public bool TryGetValue(string key, out StringValues value)
+        {
+            if (Store == null)
+            {
+                value = default(StringValues);
+                return false;
+            }
+            return Store.TryGetValue(key, out value);
+        }
+
+        /// <summary>
+        /// Returns an enumerator that iterates through a collection.
+        /// </summary>
+        /// <returns>An <see cref="Enumerator" /> object that can be used to iterate through the collection.</returns>
+        public Enumerator GetEnumerator()
+        {
+            if (Store == null || Store.Count == 0)
+            {
+                // Non-boxed Enumerator
+                return EmptyEnumerator;
+            }
+            return new Enumerator(Store.GetEnumerator());
+        }
+
+        /// <summary>
+        /// Returns an enumerator that iterates through a collection.
+        /// </summary>
+        /// <returns>An <see cref="IEnumerator" /> object that can be used to iterate through the collection.</returns>
+        IEnumerator<KeyValuePair<string, StringValues>> IEnumerable<KeyValuePair<string, StringValues>>.GetEnumerator()
+        {
+            if (Store == null || Store.Count == 0)
+            {
+                // Non-boxed Enumerator
+                return EmptyIEnumeratorType;
+            }
+            return Store.GetEnumerator();
+        }
+
+        /// <summary>
+        /// Returns an enumerator that iterates through a collection.
+        /// </summary>
+        /// <returns>An <see cref="IEnumerator" /> object that can be used to iterate through the collection.</returns>
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            if (Store == null || Store.Count == 0)
+            {
+                // Non-boxed Enumerator
+                return EmptyIEnumerator;
+            }
+            return Store.GetEnumerator();
+        }
+
+        private void ThrowIfReadOnly()
+        {
+            if (IsReadOnly)
+            {
+                throw new InvalidOperationException("The response headers cannot be modified because the response has already started.");
+            }
+        }
+
+        public struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
+        {
+            // Do NOT make this readonly, or MoveNext will not work
+            private Dictionary<string, StringValues>.Enumerator _dictionaryEnumerator;
+            private bool _notEmpty;
+
+            internal Enumerator(Dictionary<string, StringValues>.Enumerator dictionaryEnumerator)
+            {
+                _dictionaryEnumerator = dictionaryEnumerator;
+                _notEmpty = true;
+            }
+
+            public bool MoveNext()
+            {
+                if (_notEmpty)
+                {
+                    return _dictionaryEnumerator.MoveNext();
+                }
+                return false;
+            }
+
+            public KeyValuePair<string, StringValues> Current
+            {
+                get
+                {
+                    if (_notEmpty)
+                    {
+                        return _dictionaryEnumerator.Current;
+                    }
+                    return default(KeyValuePair<string, StringValues>);
+                }
+            }
+
+            public void Dispose()
+            {
+            }
+
+            object IEnumerator.Current
+            {
+                get
+                {
+                    return Current;
+                }
+            }
+
+            void IEnumerator.Reset()
+            {
+                if (_notEmpty)
+                {
+                    ((IEnumerator)_dictionaryEnumerator).Reset();
+                }
+            }
+        }
+    }
+}
diff --git a/src/Http/Http/src/HttpContextAccessor.cs b/src/Http/Http/src/HttpContextAccessor.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5a4676234cdb05f9a2e78a031d7f09837e11fd6a
--- /dev/null
+++ b/src/Http/Http/src/HttpContextAccessor.cs
@@ -0,0 +1,24 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public class HttpContextAccessor : IHttpContextAccessor
+    {
+        private static AsyncLocal<HttpContext> _httpContextCurrent = new AsyncLocal<HttpContext>();
+
+        public HttpContext HttpContext
+        {
+            get
+            {
+                return _httpContextCurrent.Value;
+            }
+            set
+            {
+                _httpContextCurrent.Value = value;
+            }
+        }
+    }
+}
diff --git a/src/Http/Http/src/HttpContextFactory.cs b/src/Http/Http/src/HttpContextFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8236a388a564f1c3828a6724ad720bfbf2362e1c
--- /dev/null
+++ b/src/Http/Http/src/HttpContextFactory.cs
@@ -0,0 +1,58 @@
+// 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.Http.Features;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public class HttpContextFactory : IHttpContextFactory
+    {
+        private readonly IHttpContextAccessor _httpContextAccessor;
+        private readonly FormOptions _formOptions;
+
+        public HttpContextFactory(IOptions<FormOptions> formOptions)
+            : this(formOptions, httpContextAccessor: null)
+        {
+        }
+
+        public HttpContextFactory(IOptions<FormOptions> formOptions, IHttpContextAccessor httpContextAccessor)
+        {
+            if (formOptions == null)
+            {
+                throw new ArgumentNullException(nameof(formOptions));
+            }
+
+            _formOptions = formOptions.Value;
+            _httpContextAccessor = httpContextAccessor;
+        }
+
+        public HttpContext Create(IFeatureCollection featureCollection)
+        {
+            if (featureCollection == null)
+            {
+                throw new ArgumentNullException(nameof(featureCollection));
+            }
+
+            var httpContext = new DefaultHttpContext(featureCollection);
+            if (_httpContextAccessor != null)
+            {
+                _httpContextAccessor.HttpContext = httpContext;
+            }
+
+            var formFeature = new FormFeature(httpContext.Request, _formOptions);
+            featureCollection.Set<IFormFeature>(formFeature);
+
+            return httpContext;
+        }
+
+        public void Dispose(HttpContext httpContext)
+        {
+            if (_httpContextAccessor != null)
+            {
+                _httpContextAccessor.HttpContext = null;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/HttpServiceCollectionExtensions.cs b/src/Http/Http/src/HttpServiceCollectionExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..cccfe6d4e6f1723308ddcf2229b2b40dabe289f1
--- /dev/null
+++ b/src/Http/Http/src/HttpServiceCollectionExtensions.cs
@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace Microsoft.Extensions.DependencyInjection
+{
+    /// <summary>
+    /// Extension methods for configuring HttpContext services.
+    /// </summary>
+    public static class HttpServiceCollectionExtensions
+    {
+        /// <summary>
+        /// Adds a default implementation for the <see cref="IHttpContextAccessor"/> service.
+        /// </summary>
+        /// <param name="services">The <see cref="IServiceCollection"/>.</param>
+        /// <returns>The service collection.</returns>
+        public static IServiceCollection AddHttpContextAccessor(this IServiceCollection services)
+        {
+            if (services == null)
+            {
+                throw new ArgumentNullException(nameof(services));
+            }
+
+            services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
+            return services;
+        }
+    }
+}
diff --git a/src/Http/Http/src/Internal/ApplicationBuilder.cs b/src/Http/Http/src/Internal/ApplicationBuilder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d0b6b6f6bfc806824bd0a6545855958957ac47a8
--- /dev/null
+++ b/src/Http/Http/src/Internal/ApplicationBuilder.cs
@@ -0,0 +1,96 @@
+// 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.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Http.Internal;
+using Microsoft.Extensions.Internal;
+
+namespace Microsoft.AspNetCore.Builder.Internal
+{
+    public class ApplicationBuilder : IApplicationBuilder
+    {
+        private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();
+
+        public ApplicationBuilder(IServiceProvider serviceProvider)
+        {
+            Properties = new Dictionary<string, object>(StringComparer.Ordinal);
+            ApplicationServices = serviceProvider;
+        }
+
+        public ApplicationBuilder(IServiceProvider serviceProvider, object server)
+            : this(serviceProvider)
+        {
+            SetProperty(Constants.BuilderProperties.ServerFeatures, server);
+        }
+
+        private ApplicationBuilder(ApplicationBuilder builder)
+        {
+            Properties = new CopyOnWriteDictionary<string, object>(builder.Properties, StringComparer.Ordinal);
+        }
+
+        public IServiceProvider ApplicationServices
+        {
+            get
+            {
+                return GetProperty<IServiceProvider>(Constants.BuilderProperties.ApplicationServices);
+            }
+            set
+            {
+                SetProperty<IServiceProvider>(Constants.BuilderProperties.ApplicationServices, value);
+            }
+        }
+
+        public IFeatureCollection ServerFeatures
+        {
+            get
+            {
+                return GetProperty<IFeatureCollection>(Constants.BuilderProperties.ServerFeatures);
+            }
+        }
+
+        public IDictionary<string, object> Properties { get; }
+
+        private T GetProperty<T>(string key)
+        {
+            object value;
+            return Properties.TryGetValue(key, out value) ? (T)value : default(T);
+        }
+
+        private void SetProperty<T>(string key, T value)
+        {
+            Properties[key] = value;
+        }
+
+        public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
+        {
+            _components.Add(middleware);
+            return this;
+        }
+
+        public IApplicationBuilder New()
+        {
+            return new ApplicationBuilder(this);
+        }
+
+        public RequestDelegate Build()
+        {
+            RequestDelegate app = context =>
+            {
+                context.Response.StatusCode = 404;
+                return Task.CompletedTask;
+            };
+
+            foreach (var component in _components.Reverse())
+            {
+                app = component(app);
+            }
+
+            return app;
+        }
+    }
+}
diff --git a/src/Http/Http/src/Internal/BindingAddress.cs b/src/Http/Http/src/Internal/BindingAddress.cs
new file mode 100644
index 0000000000000000000000000000000000000000..492fa23dbe19971e6ee27059a9d76afe896a3eda
--- /dev/null
+++ b/src/Http/Http/src/Internal/BindingAddress.cs
@@ -0,0 +1,155 @@
+// 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.Globalization;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    public class BindingAddress
+    {
+        public string Host { get; private set; }
+        public string PathBase { get; private set; }
+        public int Port { get; internal set; }
+        public string Scheme { get; private set; }
+
+        public bool IsUnixPipe
+        {
+            get
+            {
+                return Host.StartsWith(Constants.UnixPipeHostPrefix, StringComparison.Ordinal);
+            }
+        }
+
+        public string UnixPipePath
+        {
+            get
+            {
+                if (!IsUnixPipe)
+                {
+                    throw new InvalidOperationException("Binding address is not a unix pipe.");
+                }
+
+                return Host.Substring(Constants.UnixPipeHostPrefix.Length - 1);
+            }
+        }
+
+        public override string ToString()
+        {
+            if (IsUnixPipe)
+            {
+                return Scheme.ToLowerInvariant() + "://" + Host.ToLowerInvariant();
+            }
+            else
+            {
+                return Scheme.ToLowerInvariant() + "://" + Host.ToLowerInvariant() + ":" + Port.ToString(CultureInfo.InvariantCulture) + PathBase.ToString(CultureInfo.InvariantCulture);
+            }
+        }
+
+        public override int GetHashCode()
+        {
+            return ToString().GetHashCode();
+        }
+
+        public override bool Equals(object obj)
+        {
+            var other = obj as BindingAddress;
+            if (other == null)
+            {
+                return false;
+            }
+            return string.Equals(Scheme, other.Scheme, StringComparison.OrdinalIgnoreCase)
+                && string.Equals(Host, other.Host, StringComparison.OrdinalIgnoreCase)
+                && Port == other.Port
+                && PathBase == other.PathBase;
+        }
+
+        public static BindingAddress Parse(string address)
+        {
+            address = address ?? string.Empty;
+
+            int schemeDelimiterStart = address.IndexOf("://", StringComparison.Ordinal);
+            if (schemeDelimiterStart < 0)
+            {
+                throw new FormatException($"Invalid url: '{address}'");
+            }
+            int schemeDelimiterEnd = schemeDelimiterStart + "://".Length;
+
+            var isUnixPipe = address.IndexOf(Constants.UnixPipeHostPrefix, schemeDelimiterEnd, StringComparison.Ordinal) == schemeDelimiterEnd;
+
+            int pathDelimiterStart;
+            int pathDelimiterEnd;
+            if (!isUnixPipe)
+            {
+                pathDelimiterStart = address.IndexOf("/", schemeDelimiterEnd, StringComparison.Ordinal);
+                pathDelimiterEnd = pathDelimiterStart;
+            }
+            else
+            {
+                pathDelimiterStart = address.IndexOf(":", schemeDelimiterEnd + Constants.UnixPipeHostPrefix.Length, StringComparison.Ordinal);
+                pathDelimiterEnd = pathDelimiterStart + ":".Length;
+            }
+
+            if (pathDelimiterStart < 0)
+            {
+                pathDelimiterStart = pathDelimiterEnd = address.Length;
+            }
+
+            var serverAddress = new BindingAddress();
+            serverAddress.Scheme = address.Substring(0, schemeDelimiterStart);
+
+            var hasSpecifiedPort = false;
+            if (!isUnixPipe)
+            {
+                int portDelimiterStart = address.LastIndexOf(":", pathDelimiterStart - 1, pathDelimiterStart - schemeDelimiterEnd, StringComparison.Ordinal);
+                if (portDelimiterStart >= 0)
+                {
+                    int portDelimiterEnd = portDelimiterStart + ":".Length;
+
+                    string portString = address.Substring(portDelimiterEnd, pathDelimiterStart - portDelimiterEnd);
+                    int portNumber;
+                    if (int.TryParse(portString, NumberStyles.Integer, CultureInfo.InvariantCulture, out portNumber))
+                    {
+                        hasSpecifiedPort = true;
+                        serverAddress.Host = address.Substring(schemeDelimiterEnd, portDelimiterStart - schemeDelimiterEnd);
+                        serverAddress.Port = portNumber;
+                    }
+                }
+
+                if (!hasSpecifiedPort)
+                {
+                    if (string.Equals(serverAddress.Scheme, "http", StringComparison.OrdinalIgnoreCase))
+                    {
+                        serverAddress.Port = 80;
+                    }
+                    else if (string.Equals(serverAddress.Scheme, "https", StringComparison.OrdinalIgnoreCase))
+                    {
+                        serverAddress.Port = 443;
+                    }
+                }
+            }
+
+            if (!hasSpecifiedPort)
+            {
+                serverAddress.Host = address.Substring(schemeDelimiterEnd, pathDelimiterStart - schemeDelimiterEnd);
+            }
+
+            if (string.IsNullOrEmpty(serverAddress.Host))
+            {
+                throw new FormatException($"Invalid url: '{address}'");
+            }
+
+            if (address[address.Length - 1] == '/')
+            {
+                serverAddress.PathBase = address.Substring(pathDelimiterEnd, address.Length - pathDelimiterEnd - 1);
+            }
+            else
+            {
+                serverAddress.PathBase = address.Substring(pathDelimiterEnd);
+            }
+
+            return serverAddress;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/Internal/BufferingHelper.cs b/src/Http/Http/src/Internal/BufferingHelper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b912f37116b28d5d9daaebb59fc8343a4839894d
--- /dev/null
+++ b/src/Http/Http/src/Internal/BufferingHelper.cs
@@ -0,0 +1,80 @@
+// 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 Microsoft.AspNetCore.WebUtilities;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    public static class BufferingHelper
+    {
+        internal const int DefaultBufferThreshold = 1024 * 30;
+
+        private readonly static Func<string> _getTempDirectory = () => TempDirectory;
+
+        private static string _tempDirectory;
+
+        public static string TempDirectory
+        {
+            get
+            {
+                if (_tempDirectory == null)
+                {
+                    // Look for folders in the following order.
+                    var temp = Environment.GetEnvironmentVariable("ASPNETCORE_TEMP") ??     // ASPNETCORE_TEMP - User set temporary location.
+                               Path.GetTempPath();                                      // Fall back.
+
+                    if (!Directory.Exists(temp))
+                    {
+                        // TODO: ???
+                        throw new DirectoryNotFoundException(temp);
+                    }
+
+                    _tempDirectory = temp;
+                }
+
+                return _tempDirectory;
+            }
+        }
+
+        public static HttpRequest EnableRewind(this HttpRequest request, int bufferThreshold = DefaultBufferThreshold, long? bufferLimit = null)
+        {
+            if (request == null)
+            {
+                throw new ArgumentNullException(nameof(request));
+            }
+
+            var body = request.Body;
+            if (!body.CanSeek)
+            {
+                var fileStream = new FileBufferingReadStream(body, bufferThreshold, bufferLimit, _getTempDirectory);
+                request.Body = fileStream;
+                request.HttpContext.Response.RegisterForDispose(fileStream);
+            }
+            return request;
+        }
+
+        public static MultipartSection EnableRewind(this MultipartSection section, Action<IDisposable> registerForDispose,
+            int bufferThreshold = DefaultBufferThreshold, long? bufferLimit = null)
+        {
+            if (section == null)
+            {
+                throw new ArgumentNullException(nameof(section));
+            }
+            if (registerForDispose == null)
+            {
+                throw new ArgumentNullException(nameof(registerForDispose));
+            }
+
+            var body = section.Body;
+            if (!body.CanSeek)
+            {
+                var fileStream = new FileBufferingReadStream(body, bufferThreshold, bufferLimit, _getTempDirectory);
+                section.Body = fileStream;
+                registerForDispose(fileStream);
+            }
+            return section;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/Internal/Constants.cs b/src/Http/Http/src/Internal/Constants.cs
new file mode 100644
index 0000000000000000000000000000000000000000..280011b3e010b9d5bbd79519351c20f2b3ca5b2d
--- /dev/null
+++ b/src/Http/Http/src/Internal/Constants.cs
@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    internal static class Constants
+    {
+        internal const string Http = "http";
+        internal const string Https = "https";
+        internal const string UnixPipeHostPrefix = "unix:/";
+
+        internal static class BuilderProperties
+        {
+            internal static string ServerFeatures = "server.Features";
+            internal static string ApplicationServices = "application.Services";
+        }
+    }
+}
diff --git a/src/Http/Http/src/Internal/DefaultConnectionInfo.cs b/src/Http/Http/src/Internal/DefaultConnectionInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6ae7f9fc383de31bf9aa47b18d03da44b72f04f6
--- /dev/null
+++ b/src/Http/Http/src/Internal/DefaultConnectionInfo.cs
@@ -0,0 +1,90 @@
+// 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.Security.Cryptography.X509Certificates;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    public class DefaultConnectionInfo : ConnectionInfo
+    {
+        // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
+        private readonly static Func<IFeatureCollection, IHttpConnectionFeature> _newHttpConnectionFeature = f => new HttpConnectionFeature();
+        private readonly static Func<IFeatureCollection, ITlsConnectionFeature> _newTlsConnectionFeature = f => new TlsConnectionFeature();
+
+        private FeatureReferences<FeatureInterfaces> _features;
+
+        public DefaultConnectionInfo(IFeatureCollection features)
+        {
+            Initialize(features);
+        }
+
+        public virtual void Initialize( IFeatureCollection features)
+        {
+            _features = new FeatureReferences<FeatureInterfaces>(features);
+        }
+
+        public virtual void Uninitialize()
+        {
+            _features = default(FeatureReferences<FeatureInterfaces>);
+        }
+
+        private IHttpConnectionFeature HttpConnectionFeature =>
+            _features.Fetch(ref _features.Cache.Connection, _newHttpConnectionFeature);
+
+        private ITlsConnectionFeature TlsConnectionFeature=>
+            _features.Fetch(ref _features.Cache.TlsConnection, _newTlsConnectionFeature);
+
+        /// <inheritdoc />
+        public override string Id
+        {
+            get { return HttpConnectionFeature.ConnectionId; }
+            set { HttpConnectionFeature.ConnectionId = value; }
+        }
+
+        public override IPAddress RemoteIpAddress
+        {
+            get { return HttpConnectionFeature.RemoteIpAddress; }
+            set { HttpConnectionFeature.RemoteIpAddress = value; }
+        }
+
+        public override int RemotePort
+        {
+            get { return HttpConnectionFeature.RemotePort; }
+            set { HttpConnectionFeature.RemotePort = value; }
+        }
+
+        public override IPAddress LocalIpAddress
+        {
+            get { return HttpConnectionFeature.LocalIpAddress; }
+            set { HttpConnectionFeature.LocalIpAddress = value; }
+        }
+
+        public override int LocalPort
+        {
+            get { return HttpConnectionFeature.LocalPort; }
+            set { HttpConnectionFeature.LocalPort = value; }
+        }
+
+        public override X509Certificate2 ClientCertificate
+        {
+            get { return TlsConnectionFeature.ClientCertificate; }
+            set { TlsConnectionFeature.ClientCertificate = value; }
+        }
+
+        public override Task<X509Certificate2> GetClientCertificateAsync(CancellationToken cancellationToken = new CancellationToken())
+        {
+            return TlsConnectionFeature.GetClientCertificateAsync(cancellationToken);
+        }
+
+        struct FeatureInterfaces
+        {
+            public IHttpConnectionFeature Connection;
+            public ITlsConnectionFeature TlsConnection;
+        }
+    }
+}
diff --git a/src/Http/Http/src/Internal/DefaultHttpRequest.cs b/src/Http/Http/src/Internal/DefaultHttpRequest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f216475db665bf3d28a498201779d8a2f808ba81
--- /dev/null
+++ b/src/Http/Http/src/Internal/DefaultHttpRequest.cs
@@ -0,0 +1,162 @@
+// 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;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    public class DefaultHttpRequest : HttpRequest
+    {
+        // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
+        private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;
+        private readonly static Func<IFeatureCollection, IQueryFeature> _newQueryFeature = f => new QueryFeature(f);
+        private readonly static Func<HttpRequest, IFormFeature> _newFormFeature = r => new FormFeature(r);
+        private readonly static Func<IFeatureCollection, IRequestCookiesFeature> _newRequestCookiesFeature = f => new RequestCookiesFeature(f);
+
+        private HttpContext _context;
+        private FeatureReferences<FeatureInterfaces> _features;
+
+        public DefaultHttpRequest(HttpContext context)
+        {
+            Initialize(context);
+        }
+
+        public virtual void Initialize(HttpContext context)
+        {
+            _context = context;
+            _features = new FeatureReferences<FeatureInterfaces>(context.Features);
+        }
+
+        public virtual void Uninitialize()
+        {
+            _context = null;
+            _features = default(FeatureReferences<FeatureInterfaces>);
+        }
+
+        public override HttpContext HttpContext => _context;
+
+        private IHttpRequestFeature HttpRequestFeature =>
+            _features.Fetch(ref _features.Cache.Request, _nullRequestFeature);
+
+        private IQueryFeature QueryFeature =>
+            _features.Fetch(ref _features.Cache.Query, _newQueryFeature);
+
+        private IFormFeature FormFeature =>
+            _features.Fetch(ref _features.Cache.Form, this, _newFormFeature);
+
+        private IRequestCookiesFeature RequestCookiesFeature =>
+            _features.Fetch(ref _features.Cache.Cookies, _newRequestCookiesFeature);
+
+        public override PathString PathBase
+        {
+            get { return new PathString(HttpRequestFeature.PathBase); }
+            set { HttpRequestFeature.PathBase = value.Value; }
+        }
+
+        public override PathString Path
+        {
+            get { return new PathString(HttpRequestFeature.Path); }
+            set { HttpRequestFeature.Path = value.Value; }
+        }
+
+        public override QueryString QueryString
+        {
+            get { return new QueryString(HttpRequestFeature.QueryString); }
+            set { HttpRequestFeature.QueryString = value.Value; }
+        }
+
+        public override long? ContentLength
+        {
+            get { return Headers.ContentLength; }
+            set { Headers.ContentLength = value; }
+        }
+
+        public override Stream Body
+        {
+            get { return HttpRequestFeature.Body; }
+            set { HttpRequestFeature.Body = value; }
+        }
+
+        public override string Method
+        {
+            get { return HttpRequestFeature.Method; }
+            set { HttpRequestFeature.Method = value; }
+        }
+
+        public override string Scheme
+        {
+            get { return HttpRequestFeature.Scheme; }
+            set { HttpRequestFeature.Scheme = value; }
+        }
+
+        public override bool IsHttps
+        {
+            get { return string.Equals(Constants.Https, Scheme, StringComparison.OrdinalIgnoreCase); }
+            set { Scheme = value ? Constants.Https : Constants.Http; }
+        }
+
+        public override HostString Host
+        {
+            get { return HostString.FromUriComponent(Headers["Host"]); }
+            set { Headers["Host"] = value.ToUriComponent(); }
+        }
+
+        public override IQueryCollection Query
+        {
+            get { return QueryFeature.Query; }
+            set { QueryFeature.Query = value; }
+        }
+
+        public override string Protocol
+        {
+            get { return HttpRequestFeature.Protocol; }
+            set { HttpRequestFeature.Protocol = value; }
+        }
+
+        public override IHeaderDictionary Headers
+        {
+            get { return HttpRequestFeature.Headers; }
+        }
+
+        public override IRequestCookieCollection Cookies
+        {
+            get { return RequestCookiesFeature.Cookies; }
+            set { RequestCookiesFeature.Cookies = value; }
+        }
+
+        public override string ContentType
+        {
+            get { return Headers[HeaderNames.ContentType]; }
+            set { Headers[HeaderNames.ContentType] = value; }
+        }
+
+        public override bool HasFormContentType
+        {
+            get { return FormFeature.HasFormContentType; }
+        }
+
+        public override IFormCollection Form
+        {
+            get { return FormFeature.ReadForm(); }
+            set { FormFeature.Form = value; }
+        }
+
+        public override Task<IFormCollection> ReadFormAsync(CancellationToken cancellationToken)
+        {
+            return FormFeature.ReadFormAsync(cancellationToken);
+        }
+
+        struct FeatureInterfaces
+        {
+            public IHttpRequestFeature Request;
+            public IQueryFeature Query;
+            public IFormFeature Form;
+            public IRequestCookiesFeature Cookies;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/Internal/DefaultHttpResponse.cs b/src/Http/Http/src/Internal/DefaultHttpResponse.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3ca05035f5c04dd22e2ea9d71e2fa2a8a13b4720
--- /dev/null
+++ b/src/Http/Http/src/Internal/DefaultHttpResponse.cs
@@ -0,0 +1,139 @@
+// 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.Http.Features;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    public class DefaultHttpResponse : HttpResponse
+    {
+        // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
+        private readonly static Func<IFeatureCollection, IHttpResponseFeature> _nullResponseFeature = f => null;
+        private readonly static Func<IFeatureCollection, IResponseCookiesFeature> _newResponseCookiesFeature = f => new ResponseCookiesFeature(f);
+
+        private HttpContext _context;
+        private FeatureReferences<FeatureInterfaces> _features;
+
+        public DefaultHttpResponse(HttpContext context)
+        {
+            Initialize(context);
+        }
+
+        public virtual void Initialize(HttpContext context)
+        {
+            _context = context;
+            _features = new FeatureReferences<FeatureInterfaces>(context.Features);
+        }
+
+        public virtual void Uninitialize()
+        {
+            _context = null;
+            _features = default(FeatureReferences<FeatureInterfaces>);
+        }
+
+        private IHttpResponseFeature HttpResponseFeature =>
+            _features.Fetch(ref _features.Cache.Response, _nullResponseFeature);
+
+        private IResponseCookiesFeature ResponseCookiesFeature =>
+            _features.Fetch(ref _features.Cache.Cookies, _newResponseCookiesFeature);
+
+
+        public override HttpContext HttpContext { get { return _context; } }
+
+        public override int StatusCode
+        {
+            get { return HttpResponseFeature.StatusCode; }
+            set { HttpResponseFeature.StatusCode = value; }
+        }
+
+        public override IHeaderDictionary Headers
+        {
+            get { return HttpResponseFeature.Headers; }
+        }
+
+        public override Stream Body
+        {
+            get { return HttpResponseFeature.Body; }
+            set { HttpResponseFeature.Body = value; }
+        }
+
+        public override long? ContentLength
+        {
+            get { return Headers.ContentLength; }
+            set { Headers.ContentLength = value; }
+        }
+
+        public override string ContentType
+        {
+            get
+            {
+                return Headers[HeaderNames.ContentType];
+            }
+            set
+            {
+                if (string.IsNullOrEmpty(value))
+                {
+                    HttpResponseFeature.Headers.Remove(HeaderNames.ContentType);
+                }
+                else
+                {
+                    HttpResponseFeature.Headers[HeaderNames.ContentType] = value;
+                }
+            }
+        }
+
+        public override IResponseCookies Cookies
+        {
+            get { return ResponseCookiesFeature.Cookies; }
+        }
+
+        public override bool HasStarted
+        {
+            get { return HttpResponseFeature.HasStarted; }
+        }
+
+        public override void OnStarting(Func<object, Task> callback, object state)
+        {
+            if (callback == null)
+            {
+                throw new ArgumentNullException(nameof(callback));
+            }
+
+            HttpResponseFeature.OnStarting(callback, state);
+        }
+
+        public override void OnCompleted(Func<object, Task> callback, object state)
+        {
+            if (callback == null)
+            {
+                throw new ArgumentNullException(nameof(callback));
+            }
+
+            HttpResponseFeature.OnCompleted(callback, state);
+        }
+
+        public override void Redirect(string location, bool permanent)
+        {
+            if (permanent)
+            {
+                HttpResponseFeature.StatusCode = 301;
+            }
+            else
+            {
+                HttpResponseFeature.StatusCode = 302;
+            }
+
+            Headers[HeaderNames.Location] = location;
+        }
+
+        struct FeatureInterfaces
+        {
+            public IHttpResponseFeature Response;
+            public IResponseCookiesFeature Cookies;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/Internal/DefaultWebSocketManager.cs b/src/Http/Http/src/Internal/DefaultWebSocketManager.cs
new file mode 100644
index 0000000000000000000000000000000000000000..477282408d7cad42199542dba8f4fd0949fc83ca
--- /dev/null
+++ b/src/Http/Http/src/Internal/DefaultWebSocketManager.cs
@@ -0,0 +1,73 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Net.WebSockets;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    public class DefaultWebSocketManager : WebSocketManager
+    {
+        // Lambdas hoisted to static readonly fields to improve inlining https://github.com/dotnet/roslyn/issues/13624
+        private readonly static Func<IFeatureCollection, IHttpRequestFeature> _nullRequestFeature = f => null;
+        private readonly static Func<IFeatureCollection, IHttpWebSocketFeature> _nullWebSocketFeature = f => null;
+
+        private FeatureReferences<FeatureInterfaces> _features;
+
+        public DefaultWebSocketManager(IFeatureCollection features)
+        {
+            Initialize(features);
+        }
+
+        public virtual void Initialize(IFeatureCollection features)
+        {
+            _features = new FeatureReferences<FeatureInterfaces>(features);
+        }
+
+        public virtual void Uninitialize()
+        {
+            _features = default(FeatureReferences<FeatureInterfaces>);
+        }
+
+        private IHttpRequestFeature HttpRequestFeature =>
+            _features.Fetch(ref _features.Cache.Request, _nullRequestFeature);
+
+        private IHttpWebSocketFeature WebSocketFeature =>
+            _features.Fetch(ref _features.Cache.WebSockets, _nullWebSocketFeature);
+
+        public override bool IsWebSocketRequest
+        {
+            get
+            {
+                return WebSocketFeature != null && WebSocketFeature.IsWebSocketRequest;
+            }
+        }
+
+        public override IList<string> WebSocketRequestedProtocols
+        {
+            get
+            {
+                return ParsingHelpers.GetHeaderSplit(HttpRequestFeature.Headers, HeaderNames.WebSocketSubProtocols);
+            }
+        }
+
+        public override Task<WebSocket> AcceptWebSocketAsync(string subProtocol)
+        {
+            if (WebSocketFeature == null)
+            {
+                throw new NotSupportedException("WebSockets are not supported");
+            }
+            return WebSocketFeature.AcceptAsync(new WebSocketAcceptContext() { SubProtocol = subProtocol });
+        }
+
+        struct FeatureInterfaces
+        {
+            public IHttpRequestFeature Request;
+            public IHttpWebSocketFeature WebSockets;
+        }
+    }
+}
diff --git a/src/Http/Http/src/Internal/FormFile.cs b/src/Http/Http/src/Internal/FormFile.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b4a3f4d91f40082e62e062c3db993db45adce0fb
--- /dev/null
+++ b/src/Http/Http/src/Internal/FormFile.cs
@@ -0,0 +1,109 @@
+// 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;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    public class FormFile : IFormFile
+    {
+        // Stream.CopyTo method uses 80KB as the default buffer size.
+        private const int DefaultBufferSize = 80 * 1024;
+
+        private readonly Stream _baseStream;
+        private readonly long _baseStreamOffset;
+
+        public FormFile(Stream baseStream, long baseStreamOffset, long length, string name, string fileName)
+        {
+            _baseStream = baseStream;
+            _baseStreamOffset = baseStreamOffset;
+            Length = length;
+            Name = name;
+            FileName = fileName;
+        }
+
+        /// <summary>
+        /// Gets the raw Content-Disposition header of the uploaded file.
+        /// </summary>
+        public string ContentDisposition
+        {
+            get { return Headers["Content-Disposition"]; }
+            set { Headers["Content-Disposition"] = value; }
+        }
+
+        /// <summary>
+        /// Gets the raw Content-Type header of the uploaded file.
+        /// </summary>
+        public string ContentType
+        {
+            get { return Headers["Content-Type"]; }
+            set { Headers["Content-Type"] = value; }
+        }
+
+        /// <summary>
+        /// Gets the header dictionary of the uploaded file.
+        /// </summary>
+        public IHeaderDictionary Headers { get; set; }
+
+        /// <summary>
+        /// Gets the file length in bytes.
+        /// </summary>
+        public long Length { get; }
+
+        /// <summary>
+        /// Gets the name from the Content-Disposition header.
+        /// </summary>
+        public string Name { get; }
+
+        /// <summary>
+        /// Gets the file name from the Content-Disposition header.
+        /// </summary>
+        public string FileName { get; }
+
+        /// <summary>
+        /// Opens the request stream for reading the uploaded file.
+        /// </summary>
+        public Stream OpenReadStream()
+        {
+            return new ReferenceReadStream(_baseStream, _baseStreamOffset, Length);
+        }
+
+        /// <summary>
+        /// Copies the contents of the uploaded file to the <paramref name="target"/> stream.
+        /// </summary>
+        /// <param name="target">The stream to copy the file contents to.</param>
+        public void CopyTo(Stream target)
+        {
+            if (target == null)
+            {
+                throw new ArgumentNullException(nameof(target));
+            }
+
+            using (var readStream = OpenReadStream())
+            {
+                readStream.CopyTo(target, DefaultBufferSize);
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously copies the contents of the uploaded file to the <paramref name="target"/> stream.
+        /// </summary>
+        /// <param name="target">The stream to copy the file contents to.</param>
+        /// <param name="cancellationToken"></param>
+        public async Task CopyToAsync(Stream target, CancellationToken cancellationToken = default(CancellationToken))
+        {
+            if (target == null)
+            {
+                throw new ArgumentNullException(nameof(target));
+            }
+
+            using (var readStream = OpenReadStream())
+            {
+                await readStream.CopyToAsync(target, DefaultBufferSize, cancellationToken);
+            }
+        }
+    }
+}
diff --git a/src/Http/Http/src/Internal/FormFileCollection.cs b/src/Http/Http/src/Internal/FormFileCollection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..806e756a8e06f64c717402ee23e35226623936b8
--- /dev/null
+++ b/src/Http/Http/src/Internal/FormFileCollection.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.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    public class FormFileCollection : List<IFormFile>, IFormFileCollection
+    {
+        public IFormFile this[string name] => GetFile(name);
+
+        public IFormFile GetFile(string name)
+        {
+            foreach (var file in this)
+            {
+                if (string.Equals(name, file.Name, StringComparison.OrdinalIgnoreCase))
+                {
+                    return file;
+                }
+            }
+
+            return null;
+        }
+
+        public IReadOnlyList<IFormFile> GetFiles(string name)
+        {
+            var files = new List<IFormFile>();
+
+            foreach (var file in this)
+            {
+                if (string.Equals(name, file.Name, StringComparison.OrdinalIgnoreCase))
+                {
+                    files.Add(file);
+                }
+            }
+
+            return files;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/Internal/ItemsDictionary.cs b/src/Http/Http/src/Internal/ItemsDictionary.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4821912240c075e6402b731312d0e35833d6c213
--- /dev/null
+++ b/src/Http/Http/src/Internal/ItemsDictionary.cs
@@ -0,0 +1,118 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    public class ItemsDictionary : IDictionary<object, object>
+    {
+        public ItemsDictionary()
+            : this(new Dictionary<object, object>())
+        {
+        }
+
+        public ItemsDictionary(IDictionary<object, object> items)
+        {
+            Items = items;
+        }
+
+        public IDictionary<object, object> Items { get; }
+
+        // Replace the indexer with one that returns null for missing values
+        object IDictionary<object, object>.this[object key]
+        {
+            get
+            {
+                object value;
+                if (Items.TryGetValue(key, out value))
+                {
+                    return value;
+                }
+                return null;
+            }
+            set { Items[key] = value; }
+        }
+
+        void IDictionary<object, object>.Add(object key, object value)
+        {
+            Items.Add(key, value);
+        }
+
+        bool IDictionary<object, object>.ContainsKey(object key)
+        {
+            return Items.ContainsKey(key);
+        }
+
+        ICollection<object> IDictionary<object, object>.Keys
+        {
+            get { return Items.Keys; }
+        }
+
+        bool IDictionary<object, object>.Remove(object key)
+        {
+            return Items.Remove(key);
+        }
+
+        bool IDictionary<object, object>.TryGetValue(object key, out object value)
+        {
+            return Items.TryGetValue(key, out value);
+        }
+
+        ICollection<object> IDictionary<object, object>.Values
+        {
+            get { return Items.Values; }
+        }
+
+        void ICollection<KeyValuePair<object, object>>.Add(KeyValuePair<object, object> item)
+        {
+            Items.Add(item);
+        }
+
+        void ICollection<KeyValuePair<object, object>>.Clear()
+        {
+            Items.Clear();
+        }
+
+        bool ICollection<KeyValuePair<object, object>>.Contains(KeyValuePair<object, object> item)
+        {
+            return Items.Contains(item);
+        }
+
+        void ICollection<KeyValuePair<object, object>>.CopyTo(KeyValuePair<object, object>[] array, int arrayIndex)
+        {
+            Items.CopyTo(array, arrayIndex);
+        }
+
+        int ICollection<KeyValuePair<object, object>>.Count
+        {
+            get { return Items.Count; }
+        }
+
+        bool ICollection<KeyValuePair<object, object>>.IsReadOnly
+        {
+            get { return Items.IsReadOnly; }
+        }
+
+        bool ICollection<KeyValuePair<object, object>>.Remove(KeyValuePair<object, object> item)
+        {
+            object value;
+            if (Items.TryGetValue(item.Key, out value) && Equals(item.Value, value))
+            {
+                return Items.Remove(item.Key);
+            }
+            return false;
+        }
+
+        IEnumerator<KeyValuePair<object, object>> IEnumerable<KeyValuePair<object, object>>.GetEnumerator()
+        {
+            return Items.GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return Items.GetEnumerator();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/Internal/QueryCollection.cs b/src/Http/Http/src/Internal/QueryCollection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..620de44a9255a299b83d14e89a4f196a13076e26
--- /dev/null
+++ b/src/Http/Http/src/Internal/QueryCollection.cs
@@ -0,0 +1,222 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    /// <summary>
+    /// The HttpRequest query string collection
+    /// </summary>
+    public class QueryCollection : IQueryCollection
+    {
+        public static readonly QueryCollection Empty = new QueryCollection();
+        private static readonly string[] EmptyKeys = Array.Empty<string>();
+        private static readonly StringValues[] EmptyValues = Array.Empty<StringValues>();
+        private static readonly Enumerator EmptyEnumerator = new Enumerator();
+        // Pre-box
+        private static readonly IEnumerator<KeyValuePair<string, StringValues>> EmptyIEnumeratorType = EmptyEnumerator;
+        private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator;
+
+        private Dictionary<string, StringValues> Store { get; set; }
+
+        public QueryCollection()
+        {
+        }
+
+        public QueryCollection(Dictionary<string, StringValues> store)
+        {
+            Store = store;
+        }
+
+        public QueryCollection(QueryCollection store)
+        {
+            Store = store.Store;
+        }
+
+        public QueryCollection(int capacity)
+        {
+            Store = new Dictionary<string, StringValues>(capacity, StringComparer.OrdinalIgnoreCase);
+        }
+
+        /// <summary>
+        /// Get or sets the associated value from the collection as a single string.
+        /// </summary>
+        /// <param name="key">The key name.</param>
+        /// <returns>the associated value from the collection as a StringValues or StringValues.Empty if the key is not present.</returns>
+        public StringValues this[string key]
+        {
+            get
+            {
+                if (Store == null)
+                {
+                    return StringValues.Empty;
+                }
+
+                StringValues value;
+                if (TryGetValue(key, out value))
+                {
+                    return value;
+                }
+                return StringValues.Empty;
+            }
+        }
+
+        /// <summary>
+        /// Gets the number of elements contained in the <see cref="QueryCollection" />;.
+        /// </summary>
+        /// <returns>The number of elements contained in the <see cref="QueryCollection" />.</returns>
+        public int Count
+        {
+            get
+            {
+                if (Store == null)
+                {
+                    return 0;
+                }
+                return Store.Count;
+            }
+        }
+
+        public ICollection<string> Keys
+        {
+            get
+            {
+                if (Store == null)
+                {
+                    return EmptyKeys;
+                }
+                return Store.Keys;
+            }
+        }
+
+        /// <summary>
+        /// Determines whether the <see cref="QueryCollection" /> contains a specific key.
+        /// </summary>
+        /// <param name="key">The key.</param>
+        /// <returns>true if the <see cref="QueryCollection" /> contains a specific key; otherwise, false.</returns>
+        public bool ContainsKey(string key)
+        {
+            if (Store == null)
+            {
+                return false;
+            }
+            return Store.ContainsKey(key);
+        }
+
+        /// <summary>
+        /// Retrieves a value from the collection.
+        /// </summary>
+        /// <param name="key">The key.</param>
+        /// <param name="value">The value.</param>
+        /// <returns>true if the <see cref="QueryCollection" /> contains the key; otherwise, false.</returns>
+        public bool TryGetValue(string key, out StringValues value)
+        {
+            if (Store == null)
+            {
+                value = default(StringValues);
+                return false;
+            }
+            return Store.TryGetValue(key, out value);
+        }
+
+        /// <summary>
+        /// Returns an enumerator that iterates through a collection.
+        /// </summary>
+        /// <returns>An <see cref="Enumerator" /> object that can be used to iterate through the collection.</returns>
+        public Enumerator GetEnumerator()
+        {
+            if (Store == null || Store.Count == 0)
+            {
+                // Non-boxed Enumerator
+                return EmptyEnumerator;
+            }
+            return new Enumerator(Store.GetEnumerator());
+        }
+
+        /// <summary>
+        /// Returns an enumerator that iterates through a collection.
+        /// </summary>
+        /// <returns>An <see cref="IEnumerator{T}" /> object that can be used to iterate through the collection.</returns>
+        IEnumerator<KeyValuePair<string, StringValues>> IEnumerable<KeyValuePair<string, StringValues>>.GetEnumerator()
+        {
+            if (Store == null || Store.Count == 0)
+            {
+                // Non-boxed Enumerator
+                return EmptyIEnumeratorType;
+            }
+            return Store.GetEnumerator();
+        }
+
+        /// <summary>
+        /// Returns an enumerator that iterates through a collection.
+        /// </summary>
+        /// <returns>An <see cref="IEnumerator" /> object that can be used to iterate through the collection.</returns>
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            if (Store == null || Store.Count == 0)
+            {
+                // Non-boxed Enumerator
+                return EmptyIEnumerator;
+            }
+            return Store.GetEnumerator();
+        }
+
+        public struct Enumerator : IEnumerator<KeyValuePair<string, StringValues>>
+        {
+            // Do NOT make this readonly, or MoveNext will not work
+            private Dictionary<string, StringValues>.Enumerator _dictionaryEnumerator;
+            private bool _notEmpty;
+
+            internal Enumerator(Dictionary<string, StringValues>.Enumerator dictionaryEnumerator)
+            {
+                _dictionaryEnumerator = dictionaryEnumerator;
+                _notEmpty = true;
+            }
+
+            public bool MoveNext()
+            {
+                if (_notEmpty)
+                {
+                    return _dictionaryEnumerator.MoveNext();
+                }
+                return false;
+            }
+
+            public KeyValuePair<string, StringValues> Current
+            {
+                get
+                {
+                    if (_notEmpty)
+                    {
+                        return _dictionaryEnumerator.Current;
+                    }
+                    return default(KeyValuePair<string, StringValues>);
+                }
+            }
+
+            public void Dispose()
+            {
+            }
+
+            object IEnumerator.Current
+            {
+                get
+                {
+                    return Current;
+                }
+            }
+
+            void IEnumerator.Reset()
+            {
+                if (_notEmpty)
+                {
+                    ((IEnumerator)_dictionaryEnumerator).Reset();
+                }
+            }
+        }
+    }
+}
diff --git a/src/Http/Http/src/Internal/ReferenceReadStream.cs b/src/Http/Http/src/Internal/ReferenceReadStream.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c36a59d01020cf8927c8a8dc0f3889d5a9decbf4
--- /dev/null
+++ b/src/Http/Http/src/Internal/ReferenceReadStream.cs
@@ -0,0 +1,154 @@
+// 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;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    /// <summary>
+    /// A Stream that wraps another stream starting at a certain offset and reading for the given length.
+    /// </summary>
+    internal class ReferenceReadStream : Stream
+    {
+        private readonly Stream _inner;
+        private readonly long _innerOffset;
+        private readonly long _length;
+        private long _position;
+
+        private bool _disposed;
+
+        public ReferenceReadStream(Stream inner, long offset, long length)
+        {
+            if (inner == null)
+            {
+                throw new ArgumentNullException(nameof(inner));
+            }
+
+            _inner = inner;
+            _innerOffset = offset;
+            _length = length;
+            _inner.Position = offset;
+        }
+
+        public override bool CanRead
+        {
+            get { return true; }
+        }
+
+        public override bool CanSeek
+        {
+            get { return _inner.CanSeek; }
+        }
+
+        public override bool CanWrite
+        {
+            get { return false; }
+        }
+
+        public override long Length
+        {
+            get { return _length; }
+        }
+
+        public override long Position
+        {
+            get { return _position; }
+            set
+            {
+                ThrowIfDisposed();
+                if (value < 0 || value > Length)
+                {
+                    throw new ArgumentOutOfRangeException(nameof(value), value, "The Position must be within the length of the Stream: " + Length.ToString());
+                }
+                VerifyPosition();
+                _position = value;
+                _inner.Position = _innerOffset + _position;
+            }
+        }
+
+        // Throws if the position in the underlying stream has changed without our knowledge, indicating someone else is trying
+        // to use the stream at the same time which could lead to data corruption.
+        private void VerifyPosition()
+        {
+            if (_inner.Position != _innerOffset + _position)
+            {
+                throw new InvalidOperationException("The inner stream position has changed unexpectedly.");
+            }
+        }
+
+        public override long Seek(long offset, SeekOrigin origin)
+        {
+            if (origin == SeekOrigin.Begin)
+            {
+                Position = offset;
+            }
+            else if (origin == SeekOrigin.End)
+            {
+                Position = Length + offset;
+            }
+            else // if (origin == SeekOrigin.Current)
+            {
+                Position = Position + offset;
+            }
+            return Position;
+        }
+
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            ThrowIfDisposed();
+            VerifyPosition();
+            var toRead = Math.Min(count, _length - _position);
+            var read = _inner.Read(buffer, offset, (int)toRead);
+            _position += read;
+            return read;
+        }
+
+        public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            ThrowIfDisposed();
+            VerifyPosition();
+            var toRead = Math.Min(count, _length - _position);
+            var read = await _inner.ReadAsync(buffer, offset, (int)toRead, cancellationToken);
+            _position += read;
+            return read;
+        }
+
+        public override void Write(byte[] buffer, int offset, int count)
+        {
+            throw new NotSupportedException();
+        }
+        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override void SetLength(long value)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override void Flush()
+        {
+            throw new NotSupportedException();
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing)
+            {
+                _disposed = true;
+            }
+        }
+
+        private void ThrowIfDisposed()
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(ReferenceReadStream));
+            }
+        }
+    }
+}
diff --git a/src/Http/Http/src/Internal/RequestCookieCollection.cs b/src/Http/Http/src/Internal/RequestCookieCollection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4af0a652463c474a16b950492542f6cb3d827177
--- /dev/null
+++ b/src/Http/Http/src/Internal/RequestCookieCollection.cs
@@ -0,0 +1,232 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    public class RequestCookieCollection : IRequestCookieCollection
+    {
+        public static readonly RequestCookieCollection Empty = new RequestCookieCollection();
+        private static readonly string[] EmptyKeys = Array.Empty<string>();
+        private static readonly Enumerator EmptyEnumerator = new Enumerator();
+        // Pre-box
+        private static readonly IEnumerator<KeyValuePair<string, string>> EmptyIEnumeratorType = EmptyEnumerator;
+        private static readonly IEnumerator EmptyIEnumerator = EmptyEnumerator;
+
+        private Dictionary<string, string> Store { get; set; }
+
+        public RequestCookieCollection()
+        {
+        }
+
+        public RequestCookieCollection(Dictionary<string, string> store)
+        {
+            Store = store;
+        }
+
+        public RequestCookieCollection(int capacity)
+        {
+            Store = new Dictionary<string, string>(capacity, StringComparer.OrdinalIgnoreCase);
+        }
+
+        public string this[string key]
+        {
+            get
+            {
+                if (key == null)
+                {
+                    throw new ArgumentNullException(nameof(key));
+                }
+
+                if (Store == null)
+                {
+                    return null;
+                }
+
+                string value;
+                if (TryGetValue(key, out value))
+                {
+                    return value;
+                }
+                return null;
+            }
+        }
+
+        public static RequestCookieCollection Parse(IList<string> values)
+        {
+            if (values.Count == 0)
+            {
+                return Empty;
+            }
+
+            IList<CookieHeaderValue> cookies;
+            if (CookieHeaderValue.TryParseList(values, out cookies))
+            {
+                if (cookies.Count == 0)
+                {
+                    return Empty;
+                }
+
+                var collection = new RequestCookieCollection(cookies.Count);
+                var store = collection.Store;
+                for (var i = 0; i < cookies.Count; i++)
+                {
+                    var cookie = cookies[i];
+                    var name = Uri.UnescapeDataString(cookie.Name.Value);
+                    var value = Uri.UnescapeDataString(cookie.Value.Value);
+                    store[name] = value;
+                }
+
+                return collection;
+            }
+            return Empty;
+        }
+
+        public int Count
+        {
+            get
+            {
+                if (Store == null)
+                {
+                    return 0;
+                }
+                return Store.Count;
+            }
+        }
+
+        public ICollection<string> Keys
+        {
+            get
+            {
+                if (Store == null)
+                {
+                    return EmptyKeys;
+                }
+                return Store.Keys;
+            }
+        }
+
+        public bool ContainsKey(string key)
+        {
+            if (Store == null)
+            {
+                return false;
+            }
+            return Store.ContainsKey(key);
+        }
+
+        public bool TryGetValue(string key, out string value)
+        {
+            if (Store == null)
+            {
+                value = null;
+                return false;
+            }
+            return Store.TryGetValue(key, out value);
+        }
+
+        /// <summary>
+        /// Returns an struct enumerator that iterates through a collection without boxing.
+        /// </summary>
+        /// <returns>An <see cref="Enumerator" /> object that can be used to iterate through the collection.</returns>
+        public Enumerator GetEnumerator()
+        {
+            if (Store == null || Store.Count == 0)
+            {
+                // Non-boxed Enumerator
+                return EmptyEnumerator;
+            }
+            // Non-boxed Enumerator
+            return new Enumerator(Store.GetEnumerator());
+        }
+
+        /// <summary>
+        /// Returns an enumerator that iterates through a collection, boxes in non-empty path.
+        /// </summary>
+        /// <returns>An <see cref="IEnumerator{T}" /> object that can be used to iterate through the collection.</returns>
+        IEnumerator<KeyValuePair<string, string>> IEnumerable<KeyValuePair<string, string>>.GetEnumerator()
+        {
+            if (Store == null || Store.Count == 0)
+            {
+                // Non-boxed Enumerator
+                return EmptyIEnumeratorType;
+            }
+            // Boxed Enumerator
+            return GetEnumerator();
+        }
+
+        /// <summary>
+        /// Returns an enumerator that iterates through a collection, boxes in non-empty path.
+        /// </summary>
+        /// <returns>An <see cref="IEnumerator" /> object that can be used to iterate through the collection.</returns>
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            if (Store == null || Store.Count == 0)
+            {
+                // Non-boxed Enumerator
+                return EmptyIEnumerator;
+            }
+            // Boxed Enumerator
+            return GetEnumerator();
+        }
+
+        public struct Enumerator : IEnumerator<KeyValuePair<string, string>>
+        {
+            // Do NOT make this readonly, or MoveNext will not work
+            private Dictionary<string, string>.Enumerator _dictionaryEnumerator;
+            private bool _notEmpty;
+
+            internal Enumerator(Dictionary<string, string>.Enumerator dictionaryEnumerator)
+            {
+                _dictionaryEnumerator = dictionaryEnumerator;
+                _notEmpty = true;
+            }
+
+            public bool MoveNext()
+            {
+                if (_notEmpty)
+                {
+                    return _dictionaryEnumerator.MoveNext();
+                }
+                return false;
+            }
+
+            public KeyValuePair<string, string> Current
+            {
+                get
+                {
+                    if (_notEmpty)
+                    {
+                        var current = _dictionaryEnumerator.Current;
+                        return new KeyValuePair<string, string>(current.Key, current.Value);
+                    }
+                    return default(KeyValuePair<string, string>);
+                }
+            }
+
+            object IEnumerator.Current
+            {
+                get
+                {
+                    return Current;
+                }
+            }
+
+            public void Dispose()
+            {
+            }
+
+            public void Reset()
+            {
+                if (_notEmpty)
+                {
+                    ((IEnumerator)_dictionaryEnumerator).Reset();
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/src/Internal/ResponseCookies.cs b/src/Http/Http/src/Internal/ResponseCookies.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7c6e3e033ba04f133d7d1e2222ed26a175db1fb1
--- /dev/null
+++ b/src/Http/Http/src/Internal/ResponseCookies.cs
@@ -0,0 +1,139 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.Extensions.ObjectPool;
+using Microsoft.Extensions.Primitives;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    /// <summary>
+    /// A wrapper for the response Set-Cookie header.
+    /// </summary>
+    public class ResponseCookies : IResponseCookies
+    {
+        /// <summary>
+        /// Create a new wrapper.
+        /// </summary>
+        /// <param name="headers">The <see cref="IHeaderDictionary"/> for the response.</param>
+        /// <param name="builderPool">The <see cref="ObjectPool{T}"/>, if available.</param>
+        public ResponseCookies(IHeaderDictionary headers, ObjectPool<StringBuilder> builderPool)
+        {
+            if (headers == null)
+            {
+                throw new ArgumentNullException(nameof(headers));
+            }
+
+            Headers = headers;
+        }
+
+        private IHeaderDictionary Headers { get; set; }
+
+        /// <inheritdoc />
+        public void Append(string key, string value)
+        {
+            var setCookieHeaderValue = new SetCookieHeaderValue(
+                Uri.EscapeDataString(key),
+                Uri.EscapeDataString(value))
+            {
+                Path = "/"
+            };
+            var cookieValue = setCookieHeaderValue.ToString();
+
+            Headers[HeaderNames.SetCookie] = StringValues.Concat(Headers[HeaderNames.SetCookie], cookieValue);
+        }
+
+        /// <inheritdoc />
+        public void Append(string key, string value, CookieOptions options)
+        {
+            if (options == null)
+            {
+                throw new ArgumentNullException(nameof(options));
+            }
+
+            var setCookieHeaderValue = new SetCookieHeaderValue(
+                Uri.EscapeDataString(key),
+                Uri.EscapeDataString(value))
+            {
+                Domain = options.Domain,
+                Path = options.Path,
+                Expires = options.Expires,
+                MaxAge = options.MaxAge,
+                Secure = options.Secure,
+                SameSite = (Net.Http.Headers.SameSiteMode)options.SameSite,
+                HttpOnly = options.HttpOnly
+            };
+
+            var cookieValue = setCookieHeaderValue.ToString();
+
+            Headers[HeaderNames.SetCookie] = StringValues.Concat(Headers[HeaderNames.SetCookie], cookieValue);
+        }
+
+        /// <inheritdoc />
+        public void Delete(string key)
+        {
+            Delete(key, new CookieOptions() { Path = "/" });
+        }
+
+        /// <inheritdoc />
+        public void Delete(string key, CookieOptions options)
+        {
+            if (options == null)
+            {
+                throw new ArgumentNullException(nameof(options));
+            }
+
+            var encodedKeyPlusEquals = Uri.EscapeDataString(key) + "=";
+            bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
+            bool pathHasValue = !string.IsNullOrEmpty(options.Path);
+
+            Func<string, string, CookieOptions, bool> rejectPredicate;
+            if (domainHasValue)
+            {
+                rejectPredicate = (value, encKeyPlusEquals, opts) =>
+                    value.StartsWith(encKeyPlusEquals, StringComparison.OrdinalIgnoreCase) &&
+                        value.IndexOf($"domain={opts.Domain}", StringComparison.OrdinalIgnoreCase) != -1;
+            }
+            else if (pathHasValue)
+            {
+                rejectPredicate = (value, encKeyPlusEquals, opts) =>
+                    value.StartsWith(encKeyPlusEquals, StringComparison.OrdinalIgnoreCase) &&
+                        value.IndexOf($"path={opts.Path}", StringComparison.OrdinalIgnoreCase) != -1;
+            }
+            else
+            {
+                rejectPredicate = (value, encKeyPlusEquals, opts) => value.StartsWith(encKeyPlusEquals, StringComparison.OrdinalIgnoreCase);
+            }
+
+            var existingValues = Headers[HeaderNames.SetCookie];
+            if (!StringValues.IsNullOrEmpty(existingValues))
+            {
+                var values = existingValues.ToArray();
+                var newValues = new List<string>();
+
+                for (var i = 0; i < values.Length; i++)
+                {
+                    if (!rejectPredicate(values[i], encodedKeyPlusEquals, options))
+                    {
+                        newValues.Add(values[i]);
+                    }
+                }
+
+                Headers[HeaderNames.SetCookie] = new StringValues(newValues.ToArray());
+            }
+
+            Append(key, string.Empty, new CookieOptions
+            {
+                Path = options.Path,
+                Domain = options.Domain,
+                Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
+                Secure = options.Secure,
+                HttpOnly = options.HttpOnly,
+                SameSite = options.SameSite
+            });
+        }
+    }
+}
diff --git a/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj b/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..4344d0ae8eabf0d0ca145732f9b7b2144c32471a
--- /dev/null
+++ b/src/Http/Http/src/Microsoft.AspNetCore.Http.csproj
@@ -0,0 +1,21 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>ASP.NET Core default HTTP feature implementations.</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <NoWarn>$(NoWarn);CS1591</NoWarn>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore</PackageTags>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Http.Abstractions" />
+    <Reference Include="Microsoft.AspNetCore.WebUtilities" />
+    <Reference Include="Microsoft.Extensions.CopyOnWriteDictionary.Sources" PrivateAssets="All" />
+    <Reference Include="Microsoft.Extensions.ObjectPool" />
+    <Reference Include="Microsoft.Extensions.Options" />
+    <Reference Include="Microsoft.Net.Http.Headers" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/Http/src/MiddlewareFactory.cs b/src/Http/Http/src/MiddlewareFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5e5cd285f4450511fe87c7037e0e8a5b5332fefa
--- /dev/null
+++ b/src/Http/Http/src/MiddlewareFactory.cs
@@ -0,0 +1,35 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public class MiddlewareFactory : IMiddlewareFactory
+    {
+        // The default middleware factory is just an IServiceProvider proxy.
+        // This should be registered as a scoped service so that the middleware instances
+        // don't end up being singletons.
+        private readonly IServiceProvider _serviceProvider;
+
+        public MiddlewareFactory(IServiceProvider serviceProvider)
+        {
+            _serviceProvider = serviceProvider;
+        }
+
+        public IMiddleware Create(Type middlewareType)
+        {
+            return _serviceProvider.GetRequiredService(middlewareType) as IMiddleware;
+        }
+
+        public void Release(IMiddleware middleware)
+        {
+            // The container owns the lifetime of the service
+        }
+    }
+}
diff --git a/src/Http/Http/src/RequestFormReaderExtensions.cs b/src/Http/Http/src/RequestFormReaderExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8675ad7f8c31bf82d8f801697330735c219161d5
--- /dev/null
+++ b/src/Http/Http/src/RequestFormReaderExtensions.cs
@@ -0,0 +1,48 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public static class RequestFormReaderExtensions
+    {
+        /// <summary>
+        /// Read the request body as a form with the given options. These options will only be used
+        /// if the form has not already been read.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <param name="options">Options for reading the form.</param>
+        /// <param name="cancellationToken"></param>
+        /// <returns>The parsed form.</returns>
+        public static Task<IFormCollection> ReadFormAsync(this HttpRequest request, FormOptions options,
+            CancellationToken cancellationToken = new CancellationToken())
+        {
+            if (request == null)
+            {
+                throw new ArgumentNullException(nameof(request));
+            }
+            if (options == null)
+            {
+                throw new ArgumentNullException(nameof(options));
+            }
+
+            if (!request.HasFormContentType)
+            {
+                throw new InvalidOperationException("Incorrect Content-Type: " + request.ContentType);
+            }
+
+            var features = request.HttpContext.Features;
+            var formFeature = features.Get<IFormFeature>();
+            if (formFeature == null || formFeature.Form == null)
+            {
+                // We haven't read the form yet, replace the reader with one using our own options.
+                features.Set<IFormFeature>(new FormFeature(request, options));
+            }
+            return request.ReadFormAsync(cancellationToken);
+        }
+    }
+}
diff --git a/src/Http/Http/src/baseline.netcore.json b/src/Http/Http/src/baseline.netcore.json
new file mode 100644
index 0000000000000000000000000000000000000000..932bd2b6e45303e32a5f503074807efb35e0ceb5
--- /dev/null
+++ b/src/Http/Http/src/baseline.netcore.json
@@ -0,0 +1,2783 @@
+{
+  "AssemblyIdentity": "Microsoft.AspNetCore.Http, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+  "Types": [
+    {
+      "Name": "Microsoft.AspNetCore.Http.DefaultHttpContext",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "BaseType": "Microsoft.AspNetCore.Http.HttpContext",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Initialize",
+          "Parameters": [
+            {
+              "Name": "features",
+              "Type": "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Uninitialize",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Features",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Request",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.HttpRequest",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Response",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.HttpResponse",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Connection",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.ConnectionInfo",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Authentication",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.Authentication.AuthenticationManager",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_WebSockets",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.WebSocketManager",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_User",
+          "Parameters": [],
+          "ReturnType": "System.Security.Claims.ClaimsPrincipal",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_User",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Items",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.Object, System.Object>",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Items",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Collections.Generic.IDictionary<System.Object, System.Object>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_RequestServices",
+          "Parameters": [],
+          "ReturnType": "System.IServiceProvider",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RequestServices",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.IServiceProvider"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_RequestAborted",
+          "Parameters": [],
+          "ReturnType": "System.Threading.CancellationToken",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RequestAborted",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_TraceIdentifier",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_TraceIdentifier",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Session",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.ISession",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Session",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.ISession"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Abort",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "InitializeHttpRequest",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.HttpRequest",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UninitializeHttpRequest",
+          "Parameters": [
+            {
+              "Name": "instance",
+              "Type": "Microsoft.AspNetCore.Http.HttpRequest"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "InitializeHttpResponse",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.HttpResponse",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UninitializeHttpResponse",
+          "Parameters": [
+            {
+              "Name": "instance",
+              "Type": "Microsoft.AspNetCore.Http.HttpResponse"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "InitializeConnectionInfo",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.ConnectionInfo",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UninitializeConnectionInfo",
+          "Parameters": [
+            {
+              "Name": "instance",
+              "Type": "Microsoft.AspNetCore.Http.ConnectionInfo"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "InitializeAuthenticationManager",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.Authentication.AuthenticationManager",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UninitializeAuthenticationManager",
+          "Parameters": [
+            {
+              "Name": "instance",
+              "Type": "Microsoft.AspNetCore.Http.Authentication.AuthenticationManager"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "InitializeWebSocketManager",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.WebSocketManager",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UninitializeWebSocketManager",
+          "Parameters": [
+            {
+              "Name": "instance",
+              "Type": "Microsoft.AspNetCore.Http.WebSocketManager"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "features",
+              "Type": "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.HttpRequestRewindExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "EnableBuffering",
+          "Parameters": [
+            {
+              "Name": "request",
+              "Type": "Microsoft.AspNetCore.Http.HttpRequest"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "EnableBuffering",
+          "Parameters": [
+            {
+              "Name": "request",
+              "Type": "Microsoft.AspNetCore.Http.HttpRequest"
+            },
+            {
+              "Name": "bufferThreshold",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "EnableBuffering",
+          "Parameters": [
+            {
+              "Name": "request",
+              "Type": "Microsoft.AspNetCore.Http.HttpRequest"
+            },
+            {
+              "Name": "bufferLimit",
+              "Type": "System.Int64"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "EnableBuffering",
+          "Parameters": [
+            {
+              "Name": "request",
+              "Type": "Microsoft.AspNetCore.Http.HttpRequest"
+            },
+            {
+              "Name": "bufferThreshold",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "bufferLimit",
+              "Type": "System.Int64"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.FormCollection",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.IFormCollection"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Files",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IFormFileCollection",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.IFormCollection",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Item",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringValues",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.IFormCollection",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Count",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.IFormCollection",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Keys",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.ICollection<System.String>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.IFormCollection",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ContainsKey",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.IFormCollection",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryGetValue",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringValues",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.IFormCollection",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetEnumerator",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.FormCollection+Enumerator",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "fields",
+              "Type": "System.Collections.Generic.Dictionary<System.String, Microsoft.Extensions.Primitives.StringValues>"
+            },
+            {
+              "Name": "files",
+              "Type": "Microsoft.AspNetCore.Http.IFormFileCollection",
+              "DefaultValue": "null"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "Empty",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.FormCollection",
+          "Static": true,
+          "ReadOnly": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.HeaderDictionary",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.IHeaderDictionary"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Keys",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.ICollection<System.String>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.IDictionary<System.String, Microsoft.Extensions.Primitives.StringValues>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Values",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.ICollection<Microsoft.Extensions.Primitives.StringValues>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.IDictionary<System.String, Microsoft.Extensions.Primitives.StringValues>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ContainsKey",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.IDictionary<System.String, Microsoft.Extensions.Primitives.StringValues>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Add",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringValues"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.IDictionary<System.String, Microsoft.Extensions.Primitives.StringValues>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Remove",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.IDictionary<System.String, Microsoft.Extensions.Primitives.StringValues>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "TryGetValue",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringValues",
+              "Direction": "Out"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.IDictionary<System.String, Microsoft.Extensions.Primitives.StringValues>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Count",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IsReadOnly",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Add",
+          "Parameters": [
+            {
+              "Name": "item",
+              "Type": "System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Clear",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Contains",
+          "Parameters": [
+            {
+              "Name": "item",
+              "Type": "System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "CopyTo",
+          "Parameters": [
+            {
+              "Name": "array",
+              "Type": "System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>[]"
+            },
+            {
+              "Name": "arrayIndex",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Remove",
+          "Parameters": [
+            {
+              "Name": "item",
+              "Type": "System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Item",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "Microsoft.Extensions.Primitives.StringValues",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.IHeaderDictionary",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Item",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "Microsoft.Extensions.Primitives.StringValues"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.IHeaderDictionary",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentLength",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Int64>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.IHeaderDictionary",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ContentLength",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.Int64>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.IHeaderDictionary",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_IsReadOnly",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetEnumerator",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.HeaderDictionary+Enumerator",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "store",
+              "Type": "System.Collections.Generic.Dictionary<System.String, Microsoft.Extensions.Primitives.StringValues>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "capacity",
+              "Type": "System.Int32"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.HttpContextAccessor",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.IHttpContextAccessor"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_HttpContext",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.HttpContext",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.IHttpContextAccessor",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_HttpContext",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.IHttpContextAccessor",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.HttpContextFactory",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.IHttpContextFactory"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Create",
+          "Parameters": [
+            {
+              "Name": "featureCollection",
+              "Type": "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.HttpContext",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.IHttpContextFactory",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Dispose",
+          "Parameters": [
+            {
+              "Name": "httpContext",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.IHttpContextFactory",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "formOptions",
+              "Type": "Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Http.Features.FormOptions>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "formOptions",
+              "Type": "Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Http.Features.FormOptions>"
+            },
+            {
+              "Name": "httpContextAccessor",
+              "Type": "Microsoft.AspNetCore.Http.IHttpContextAccessor"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.MiddlewareFactory",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.IMiddlewareFactory"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Create",
+          "Parameters": [
+            {
+              "Name": "middlewareType",
+              "Type": "System.Type"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Http.IMiddleware",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.IMiddlewareFactory",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Release",
+          "Parameters": [
+            {
+              "Name": "middleware",
+              "Type": "Microsoft.AspNetCore.Http.IMiddleware"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.IMiddlewareFactory",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "serviceProvider",
+              "Type": "System.IServiceProvider"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.RequestFormReaderExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "ReadFormAsync",
+          "Parameters": [
+            {
+              "Name": "request",
+              "Type": "Microsoft.AspNetCore.Http.HttpRequest"
+            },
+            {
+              "Name": "options",
+              "Type": "Microsoft.AspNetCore.Http.Features.FormOptions"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Http.IFormCollection>",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.DefaultSessionFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.Features.ISessionFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Session",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.ISession",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.ISessionFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Session",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.ISession"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.ISessionFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.FormFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.Features.IFormFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_HasFormContentType",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IFormFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Form",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IFormCollection",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IFormFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Form",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.IFormCollection"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IFormFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ReadForm",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IFormCollection",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IFormFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ReadFormAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Http.IFormCollection>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ReadFormAsync",
+          "Parameters": [
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.Http.IFormCollection>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IFormFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "form",
+              "Type": "Microsoft.AspNetCore.Http.IFormCollection"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "request",
+              "Type": "Microsoft.AspNetCore.Http.HttpRequest"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "request",
+              "Type": "Microsoft.AspNetCore.Http.HttpRequest"
+            },
+            {
+              "Name": "options",
+              "Type": "Microsoft.AspNetCore.Http.Features.FormOptions"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.FormOptions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_BufferBody",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_BufferBody",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MemoryBufferThreshold",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_MemoryBufferThreshold",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_BufferBodyLengthLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int64",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_BufferBodyLengthLimit",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int64"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ValueCountLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ValueCountLimit",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_KeyLengthLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_KeyLengthLimit",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ValueLengthLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ValueLengthLimit",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MultipartBoundaryLengthLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_MultipartBoundaryLengthLimit",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MultipartHeadersCountLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_MultipartHeadersCountLimit",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MultipartHeadersLengthLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_MultipartHeadersLengthLimit",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_MultipartBodyLengthLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int64",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_MultipartBodyLengthLimit",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int64"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "DefaultMemoryBufferThreshold",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "65536"
+        },
+        {
+          "Kind": "Field",
+          "Name": "DefaultBufferBodyLengthLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "134217728"
+        },
+        {
+          "Kind": "Field",
+          "Name": "DefaultMultipartBoundaryLengthLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "128"
+        },
+        {
+          "Kind": "Field",
+          "Name": "DefaultMultipartBodyLengthLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int64",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "134217728"
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.HttpConnectionFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_ConnectionId",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ConnectionId",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_LocalIpAddress",
+          "Parameters": [],
+          "ReturnType": "System.Net.IPAddress",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_LocalIpAddress",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Net.IPAddress"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_LocalPort",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_LocalPort",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_RemoteIpAddress",
+          "Parameters": [],
+          "ReturnType": "System.Net.IPAddress",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RemoteIpAddress",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Net.IPAddress"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_RemotePort",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RemotePort",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.HttpRequestFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Protocol",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Protocol",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Scheme",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Scheme",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Method",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Method",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_PathBase",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_PathBase",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Path",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Path",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_QueryString",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_QueryString",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_RawTarget",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RawTarget",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Headers",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IHeaderDictionary",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Headers",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.IHeaderDictionary"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Body",
+          "Parameters": [],
+          "ReturnType": "System.IO.Stream",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Body",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.IO.Stream"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.HttpRequestIdentifierFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.Features.IHttpRequestIdentifierFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_TraceIdentifier",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestIdentifierFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_TraceIdentifier",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestIdentifierFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.HttpRequestLifetimeFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.Features.IHttpRequestLifetimeFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_RequestAborted",
+          "Parameters": [],
+          "ReturnType": "System.Threading.CancellationToken",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestLifetimeFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RequestAborted",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestLifetimeFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Abort",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpRequestLifetimeFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.HttpResponseFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.Features.IHttpResponseFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_StatusCode",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpResponseFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_StatusCode",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpResponseFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ReasonPhrase",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpResponseFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ReasonPhrase",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpResponseFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Headers",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IHeaderDictionary",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpResponseFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Headers",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.IHeaderDictionary"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpResponseFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Body",
+          "Parameters": [],
+          "ReturnType": "System.IO.Stream",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpResponseFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Body",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.IO.Stream"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpResponseFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HasStarted",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpResponseFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnStarting",
+          "Parameters": [
+            {
+              "Name": "callback",
+              "Type": "System.Func<System.Object, System.Threading.Tasks.Task>"
+            },
+            {
+              "Name": "state",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpResponseFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "OnCompleted",
+          "Parameters": [
+            {
+              "Name": "callback",
+              "Type": "System.Func<System.Object, System.Threading.Tasks.Task>"
+            },
+            {
+              "Name": "state",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IHttpResponseFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.ItemsFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.Features.IItemsFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Items",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.Object, System.Object>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IItemsFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Items",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Collections.Generic.IDictionary<System.Object, System.Object>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IItemsFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.QueryFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.Features.IQueryFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Query",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IQueryCollection",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IQueryFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Query",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.IQueryCollection"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IQueryFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "query",
+              "Type": "Microsoft.AspNetCore.Http.IQueryCollection"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "features",
+              "Type": "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.RequestCookiesFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.Features.IRequestCookiesFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Cookies",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IRequestCookieCollection",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IRequestCookiesFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Cookies",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.IRequestCookieCollection"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IRequestCookiesFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "cookies",
+              "Type": "Microsoft.AspNetCore.Http.IRequestCookieCollection"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "features",
+              "Type": "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.ResponseCookiesFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.Features.IResponseCookiesFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Cookies",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.IResponseCookies",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IResponseCookiesFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "features",
+              "Type": "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "features",
+              "Type": "Microsoft.AspNetCore.Http.Features.IFeatureCollection"
+            },
+            {
+              "Name": "builderPool",
+              "Type": "Microsoft.Extensions.ObjectPool.ObjectPool<System.Text.StringBuilder>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.ServiceProvidersFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.Features.IServiceProvidersFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_RequestServices",
+          "Parameters": [],
+          "ReturnType": "System.IServiceProvider",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IServiceProvidersFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_RequestServices",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.IServiceProvider"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IServiceProvidersFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.TlsConnectionFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.Features.ITlsConnectionFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_ClientCertificate",
+          "Parameters": [],
+          "ReturnType": "System.Security.Cryptography.X509Certificates.X509Certificate2",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.ITlsConnectionFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ClientCertificate",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Security.Cryptography.X509Certificates.X509Certificate2"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.ITlsConnectionFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetClientCertificateAsync",
+          "Parameters": [
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Security.Cryptography.X509Certificates.X509Certificate2>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.ITlsConnectionFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.Features.Authentication.HttpAuthenticationFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.Features.Authentication.IHttpAuthenticationFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_User",
+          "Parameters": [],
+          "ReturnType": "System.Security.Claims.ClaimsPrincipal",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.Authentication.IHttpAuthenticationFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_User",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Security.Claims.ClaimsPrincipal"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.Authentication.IHttpAuthenticationFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Handler",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.Http.Features.Authentication.IAuthenticationHandler",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.Authentication.IHttpAuthenticationFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Handler",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "Microsoft.AspNetCore.Http.Features.Authentication.IAuthenticationHandler"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.Authentication.IHttpAuthenticationFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.Extensions.DependencyInjection.HttpServiceCollectionExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "AddHttpContextAccessor",
+          "Parameters": [
+            {
+              "Name": "services",
+              "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection"
+            }
+          ],
+          "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.FormCollection+Enumerator",
+      "Visibility": "Public",
+      "Kind": "Struct",
+      "Sealed": true,
+      "ImplementedInterfaces": [
+        "System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>>"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Dispose",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.IDisposable",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "MoveNext",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.IEnumerator",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Current",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Http.HeaderDictionary+Enumerator",
+      "Visibility": "Public",
+      "Kind": "Struct",
+      "Sealed": true,
+      "ImplementedInterfaces": [
+        "System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>>"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Dispose",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.IDisposable",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "MoveNext",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.IEnumerator",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Current",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<System.String, Microsoft.Extensions.Primitives.StringValues>>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/Http/Http/test/Authentication/DefaultAuthenticationManagerTests.cs b/src/Http/Http/test/Authentication/DefaultAuthenticationManagerTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..101f2b19eb4d22bdbb0c8369df20ecc837c6b5fd
--- /dev/null
+++ b/src/Http/Http/test/Authentication/DefaultAuthenticationManagerTests.cs
@@ -0,0 +1,104 @@
+// 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.
+
+#pragma warning disable CS0618 // Type or member is obsolete
+using System;
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features.Authentication;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Authentication.Internal
+{
+    public class DefaultAuthenticationManagerTests
+    {
+
+        [Fact]
+        public async Task AuthenticateWithNoAuthMiddlewareThrows()
+        {
+            var context = CreateContext();
+            await Assert.ThrowsAsync<InvalidOperationException>(async () => await context.Authentication.AuthenticateAsync("Foo"));
+        }
+
+        [Theory]
+        [InlineData("Automatic")]
+        [InlineData("Foo")]
+        public async Task ChallengeWithNoAuthMiddlewareMayThrow(string scheme)
+        {
+            var context = CreateContext();
+            await Assert.ThrowsAsync<InvalidOperationException>(() => context.Authentication.ChallengeAsync(scheme));
+        }
+
+        [Fact]
+        public async Task SignInWithNoAuthMiddlewareThrows()
+        {
+            var context = CreateContext();
+            await Assert.ThrowsAsync<InvalidOperationException>(() => context.Authentication.SignInAsync("Foo", new ClaimsPrincipal()));
+        }
+
+        [Fact]
+        public async Task SignOutWithNoAuthMiddlewareMayThrow()
+        {
+            var context = CreateContext();
+            await Assert.ThrowsAsync<InvalidOperationException>(() => context.Authentication.SignOutAsync("Foo"));
+        }
+
+        [Fact]
+        public async Task SignInOutIn()
+        {
+            var context = CreateContext();
+            var handler = new AuthHandler();
+            context.Features.Set<IHttpAuthenticationFeature>(new HttpAuthenticationFeature() { Handler = handler });
+            var user = new ClaimsPrincipal();
+            await context.Authentication.SignInAsync("ignored", user);
+            Assert.True(handler.SignedIn);
+            await context.Authentication.SignOutAsync("ignored");
+            Assert.False(handler.SignedIn);
+            await context.Authentication.SignInAsync("ignored", user);
+            Assert.True(handler.SignedIn);
+            await context.Authentication.SignOutAsync("ignored", new AuthenticationProperties() { RedirectUri = "~/logout" });
+            Assert.False(handler.SignedIn);
+        }
+
+        private class AuthHandler : IAuthenticationHandler
+        {
+            public bool SignedIn { get; set; }
+
+            public Task AuthenticateAsync(AuthenticateContext context)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task ChallengeAsync(ChallengeContext context)
+            {
+                throw new NotImplementedException();
+            }
+
+            public void GetDescriptions(DescribeSchemesContext context)
+            {
+                throw new NotImplementedException();
+            }
+
+            public Task SignInAsync(SignInContext context)
+            {
+                SignedIn = true;
+                context.Accept();
+                return Task.FromResult(0);
+            }
+
+            public Task SignOutAsync(SignOutContext context)
+            {
+                SignedIn = false;
+                context.Accept();
+                return Task.FromResult(0);
+            }
+        }
+
+        private HttpContext CreateContext()
+        {
+            var context = new DefaultHttpContext();
+            return context;
+        }
+    }
+}
+#pragma warning restore CS0618 // Type or member is obsolete
diff --git a/src/Http/Http/test/DefaultHttpContextTests.cs b/src/Http/Http/test/DefaultHttpContextTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..33f73cf191f11b0ee90896cbaf99b590ce8993f0
--- /dev/null
+++ b/src/Http/Http/test/DefaultHttpContextTests.cs
@@ -0,0 +1,352 @@
+// 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.Net.WebSockets;
+using System.Reflection;
+using System.Security.Claims;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http.Features;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public class DefaultHttpContextTests
+    {
+        [Fact]
+        public void GetOnSessionProperty_ThrowsOnMissingSessionFeature()
+        {
+            // Arrange
+            var context = new DefaultHttpContext();
+
+            // Act & Assert
+            var exception = Assert.Throws<InvalidOperationException>(() => context.Session);
+            Assert.Equal("Session has not been configured for this application or request.", exception.Message);
+        }
+
+        [Fact]
+        public void GetOnSessionProperty_ReturnsAvailableSession()
+        {
+            // Arrange
+            var context = new DefaultHttpContext();
+            var session = new TestSession();
+            session.Set("key1", null);
+            session.Set("key2", null);
+            var feature = new BlahSessionFeature();
+            feature.Session = session;
+            context.Features.Set<ISessionFeature>(feature);
+
+            // Act & Assert
+            Assert.Same(session, context.Session);
+            context.Session.Set("key3", null);
+            Assert.Equal(3, context.Session.Keys.Count());
+        }
+
+        [Fact]
+        public void AllowsSettingSession_WithoutSettingUpSessionFeature_Upfront()
+        {
+            // Arrange
+            var session = new TestSession();
+            var context = new DefaultHttpContext();
+
+            // Act
+            context.Session = session;
+
+            // Assert
+            Assert.Same(session, context.Session);
+        }
+
+        [Fact]
+        public void SettingSession_OverridesAvailableSession()
+        {
+            // Arrange
+            var context = new DefaultHttpContext();
+            var session = new TestSession();
+            session.Set("key1", null);
+            session.Set("key2", null);
+            var feature = new BlahSessionFeature();
+            feature.Session = session;
+            context.Features.Set<ISessionFeature>(feature);
+
+            // Act
+            context.Session = new TestSession();
+
+            // Assert
+            Assert.NotSame(session, context.Session);
+            Assert.Empty(context.Session.Keys);
+        }
+
+        [Fact]
+        public void EmptyUserIsNeverNull()
+        {
+            var context = new DefaultHttpContext(new FeatureCollection());
+            Assert.NotNull(context.User);
+            Assert.Single(context.User.Identities);
+            Assert.True(object.ReferenceEquals(context.User, context.User));
+            Assert.False(context.User.Identity.IsAuthenticated);
+            Assert.True(string.IsNullOrEmpty(context.User.Identity.AuthenticationType));
+
+            context.User = null;
+            Assert.NotNull(context.User);
+            Assert.Single(context.User.Identities);
+            Assert.True(object.ReferenceEquals(context.User, context.User));
+            Assert.False(context.User.Identity.IsAuthenticated);
+            Assert.True(string.IsNullOrEmpty(context.User.Identity.AuthenticationType));
+
+            context.User = new ClaimsPrincipal();
+            Assert.NotNull(context.User);
+            Assert.Empty(context.User.Identities);
+            Assert.True(object.ReferenceEquals(context.User, context.User));
+            Assert.Null(context.User.Identity);
+
+            context.User = new ClaimsPrincipal(new ClaimsIdentity("SomeAuthType"));
+            Assert.Equal("SomeAuthType", context.User.Identity.AuthenticationType);
+            Assert.True(context.User.Identity.IsAuthenticated);
+        }
+
+        [Fact]
+        public void GetItems_DefaultCollectionProvided()
+        {
+            var context = new DefaultHttpContext(new FeatureCollection());
+            Assert.Null(context.Features.Get<IItemsFeature>());
+            var items = context.Items;
+            Assert.NotNull(context.Features.Get<IItemsFeature>());
+            Assert.NotNull(items);
+            Assert.Same(items, context.Items);
+            var item = new object();
+            context.Items["foo"] = item;
+            Assert.Same(item, context.Items["foo"]);
+        }
+
+        [Fact]
+        public void GetItems_DefaultRequestIdentifierAvailable()
+        {
+            var context = new DefaultHttpContext(new FeatureCollection());
+            Assert.Null(context.Features.Get<IHttpRequestIdentifierFeature>());
+            var traceIdentifier = context.TraceIdentifier;
+            Assert.NotNull(context.Features.Get<IHttpRequestIdentifierFeature>());
+            Assert.NotNull(traceIdentifier);
+            Assert.Same(traceIdentifier, context.TraceIdentifier);
+
+            context.TraceIdentifier = "Hello";
+            Assert.Same("Hello", context.TraceIdentifier);
+        }
+
+        [Fact]
+        public void SetItems_NewCollectionUsed()
+        {
+            var context = new DefaultHttpContext(new FeatureCollection());
+            Assert.Null(context.Features.Get<IItemsFeature>());
+            var items = new Dictionary<object, object>();
+            context.Items = items;
+            Assert.NotNull(context.Features.Get<IItemsFeature>());
+            Assert.Same(items, context.Items);
+            var item = new object();
+            items["foo"] = item;
+            Assert.Same(item, context.Items["foo"]);
+        }
+
+        [Fact]
+        public void UpdateFeatures_ClearsCachedFeatures()
+        {
+            var features = new FeatureCollection();
+            features.Set<IHttpRequestFeature>(new HttpRequestFeature());
+            features.Set<IHttpResponseFeature>(new HttpResponseFeature());
+            features.Set<IHttpWebSocketFeature>(new TestHttpWebSocketFeature());
+
+            // featurecollection is set. all cached interfaces are null.
+            var context = new DefaultHttpContext(features);
+            TestAllCachedFeaturesAreNull(context, features);
+            Assert.Equal(3, features.Count());
+
+            // getting feature properties populates feature collection with defaults
+            TestAllCachedFeaturesAreSet(context, features);
+            Assert.NotEqual(3, features.Count());
+
+            // featurecollection is null. and all cached interfaces are null.
+            // only top level is tested because child objects are inaccessible.
+            context.Uninitialize();
+            TestCachedFeaturesAreNull(context, null);
+
+
+            var newFeatures = new FeatureCollection();
+            newFeatures.Set<IHttpRequestFeature>(new HttpRequestFeature());
+            newFeatures.Set<IHttpResponseFeature>(new HttpResponseFeature());
+            newFeatures.Set<IHttpWebSocketFeature>(new TestHttpWebSocketFeature());
+
+            // featurecollection is set to newFeatures. all cached interfaces are null.
+            context.Initialize(newFeatures);
+            TestAllCachedFeaturesAreNull(context, newFeatures);
+            Assert.Equal(3, newFeatures.Count());
+
+            // getting feature properties populates new feature collection with defaults
+            TestAllCachedFeaturesAreSet(context, newFeatures);
+            Assert.NotEqual(3, newFeatures.Count());
+        }
+
+        void TestAllCachedFeaturesAreNull(HttpContext context, IFeatureCollection features)
+        {
+            TestCachedFeaturesAreNull(context, features);
+            TestCachedFeaturesAreNull(context.Request, features);
+            TestCachedFeaturesAreNull(context.Response, features);
+#pragma warning disable CS0618 // Type or member is obsolete
+            TestCachedFeaturesAreNull(context.Authentication, features);
+#pragma warning restore CS0618 // Type or member is obsolete
+            TestCachedFeaturesAreNull(context.Connection, features);
+            TestCachedFeaturesAreNull(context.WebSockets, features);
+        }
+
+        void TestCachedFeaturesAreNull(object value, IFeatureCollection features)
+        {
+            var type = value.GetType();
+
+            var field = type
+                .GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
+                .Single(f =>
+                    f.FieldType.GetTypeInfo().IsGenericType &&
+                    f.FieldType.GetGenericTypeDefinition() == typeof(FeatureReferences<>));
+
+            var boxedExpectedStruct = features == null ?
+                Activator.CreateInstance(field.FieldType) :
+                Activator.CreateInstance(field.FieldType, features);
+
+            var boxedActualStruct = field.GetValue(value);
+
+            Assert.Equal(boxedExpectedStruct, boxedActualStruct);
+        }
+
+        void TestAllCachedFeaturesAreSet(HttpContext context, IFeatureCollection features)
+        {
+            TestCachedFeaturesAreSet(context, features);
+            TestCachedFeaturesAreSet(context.Request, features);
+            TestCachedFeaturesAreSet(context.Response, features);
+#pragma warning disable CS0618 // Type or member is obsolete
+            TestCachedFeaturesAreSet(context.Authentication, features);
+#pragma warning restore CS0618 // Type or member is obsolete
+            TestCachedFeaturesAreSet(context.Connection, features);
+            TestCachedFeaturesAreSet(context.WebSockets, features);
+        }
+
+        void TestCachedFeaturesAreSet(object value, IFeatureCollection features)
+        {
+            var type = value.GetType();
+
+            var properties = type
+                .GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance)
+                .Where(p => p.PropertyType.GetTypeInfo().IsInterface);
+
+            TestFeatureProperties(value, features, properties);
+
+            var fields = type
+                .GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
+                .Where(f => f.FieldType.GetTypeInfo().IsInterface);
+
+            foreach (var field in fields)
+            {
+                if (field.FieldType == typeof(IFeatureCollection))
+                {
+                    Assert.Same(features, field.GetValue(value));
+                }
+                else
+                {
+                    var v = field.GetValue(value);
+                    Assert.Same(features[field.FieldType], v);
+                    Assert.NotNull(v);
+                }
+            }
+
+        }
+
+        private static void TestFeatureProperties(object value, IFeatureCollection features, IEnumerable<PropertyInfo> properties)
+        {
+            foreach (var property in properties)
+            {
+                if (property.PropertyType == typeof(IFeatureCollection))
+                {
+                    Assert.Same(features, property.GetValue(value));
+                }
+                else
+                {
+                    if (property.Name.Contains("Feature"))
+                    {
+                        var v = property.GetValue(value);
+                        Assert.Same(features[property.PropertyType], v);
+                        Assert.NotNull(v);
+                    }
+                }
+            }
+        }
+
+        private HttpContext CreateContext()
+        {
+            var context = new DefaultHttpContext();
+            return context;
+        }
+
+        private class TestSession : ISession
+        {
+            private Dictionary<string, byte[]> _store
+                = new Dictionary<string, byte[]>(StringComparer.OrdinalIgnoreCase);
+
+            public string Id { get; set; }
+
+            public bool IsAvailable { get; } = true;
+
+            public IEnumerable<string> Keys { get { return _store.Keys; } }
+
+            public void Clear()
+            {
+                _store.Clear();
+            }
+
+            public Task CommitAsync(CancellationToken cancellationToken)
+            {
+                return Task.FromResult(0);
+            }
+
+            public Task LoadAsync(CancellationToken cancellationToken)
+            {
+                return Task.FromResult(0);
+            }
+
+            public void Remove(string key)
+            {
+                _store.Remove(key);
+            }
+
+            public void Set(string key, byte[] value)
+            {
+                _store[key] = value;
+            }
+
+            public bool TryGetValue(string key, out byte[] value)
+            {
+                return _store.TryGetValue(key, out value);
+            }
+        }
+
+        private class BlahSessionFeature : ISessionFeature
+        {
+            public ISession Session { get; set; }
+        }
+
+        private class TestHttpWebSocketFeature : IHttpWebSocketFeature
+        {
+            public bool IsWebSocketRequest
+            {
+                get
+                {
+                    throw new NotImplementedException();
+                }
+            }
+
+            public Task<WebSocket> AcceptAsync(WebSocketAcceptContext context)
+            {
+                throw new NotImplementedException();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/test/Features/FakeResponseFeature.cs b/src/Http/Http/test/Features/FakeResponseFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..43a7acab583f921f030032ee250c8cf49bae0c73
--- /dev/null
+++ b/src/Http/Http/test/Features/FakeResponseFeature.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 System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class FakeResponseFeature : HttpResponseFeature
+    {
+        List<Tuple<Func<object, Task>, object>> _onCompletedCallbacks = new List<Tuple<Func<object, Task>, object>>();
+
+        public override void OnCompleted(Func<object, Task> callback, object state)
+        {
+            _onCompletedCallbacks.Add(new Tuple<Func<object, Task>, object>(callback, state));
+        }
+
+        public async Task CompleteAsync()
+        {
+            var callbacks = _onCompletedCallbacks;
+            _onCompletedCallbacks = null;
+            foreach (var callback in callbacks)
+            {
+                await callback.Item1(callback.Item2);
+            }
+        }
+    }
+}
diff --git a/src/Http/Http/test/Features/FormFeatureTests.cs b/src/Http/Http/test/Features/FormFeatureTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..591f46a43ec9b4364d3d47c5eeff8aa83cba5e42
--- /dev/null
+++ b/src/Http/Http/test/Features/FormFeatureTests.cs
@@ -0,0 +1,521 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class FormFeatureTests
+    {
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_SimpleData_ReturnsParsedFormCollection(bool bufferRequest)
+        {
+            var formContent = Encoding.UTF8.GetBytes("foo=bar&baz=2");
+            var context = new DefaultHttpContext();
+            var responseFeature = new FakeResponseFeature();
+            context.Features.Set<IHttpResponseFeature>(responseFeature);
+            context.Request.ContentType = "application/x-www-form-urlencoded; charset=utf-8";
+            context.Request.Body = new NonSeekableReadStream(formContent);
+
+            IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
+            context.Features.Set<IFormFeature>(formFeature);
+
+            var formCollection = await context.Request.ReadFormAsync();
+
+            Assert.Equal("bar", formCollection["foo"]);
+            Assert.Equal("2", formCollection["baz"]);
+            Assert.Equal(bufferRequest, context.Request.Body.CanSeek);
+            if (bufferRequest)
+            {
+                Assert.Equal(0, context.Request.Body.Position);
+            }
+
+            // Cached
+            formFeature = context.Features.Get<IFormFeature>();
+            Assert.NotNull(formFeature);
+            Assert.NotNull(formFeature.Form);
+            Assert.Same(formFeature.Form, formCollection);
+
+            // Cleanup
+            await responseFeature.CompleteAsync();
+        }
+
+        private const string MultipartContentType = "multipart/form-data; boundary=WebKitFormBoundary5pDRpGheQXaM8k3T";
+
+        private const string MultipartContentTypeWithSpecialCharacters = "multipart/form-data; boundary=\"WebKitFormBoundary/:5pDRpGheQXaM8k3T\"";
+
+        private const string EmptyMultipartForm = "--WebKitFormBoundary5pDRpGheQXaM8k3T--";
+
+        // Note that CRLF (\r\n) is required. You can't use multi-line C# strings here because the line breaks on Linux are just LF.
+        private const string MultipartFormEnd = "--WebKitFormBoundary5pDRpGheQXaM8k3T--\r\n";
+
+        private const string MultipartFormEndWithSpecialCharacters = "--WebKitFormBoundary/:5pDRpGheQXaM8k3T--\r\n";
+
+        private const string MultipartFormField = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
+"Content-Disposition: form-data; name=\"description\"\r\n" +
+"\r\n" +
+"Foo\r\n";
+
+        private const string MultipartFormFile = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
+"Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"\r\n" +
+"Content-Type: text/html\r\n" +
+"\r\n" +
+"<html><body>Hello World</body></html>\r\n";
+
+        private const string MultipartFormEncodedFilename = "--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
+"Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"; filename*=utf-8\'\'t%c3%a9mp.html\r\n" +
+"Content-Type: text/html\r\n" +
+"\r\n" +
+"<html><body>Hello World</body></html>\r\n";
+
+        private const string MultipartFormFileSpecialCharacters = "--WebKitFormBoundary/:5pDRpGheQXaM8k3T\r\n" +
+"Content-Disposition: form-data; name=\"description\"\r\n" +
+"\r\n" +
+"Foo\r\n";
+
+
+        private const string MultipartFormWithField =
+            MultipartFormField +
+            MultipartFormEnd;
+
+        private const string MultipartFormWithFile =
+            MultipartFormFile +
+            MultipartFormEnd;
+
+        private const string MultipartFormWithFieldAndFile =
+            MultipartFormField +
+            MultipartFormFile +
+            MultipartFormEnd;
+
+        private const string MultipartFormWithEncodedFilename =
+            MultipartFormEncodedFilename +
+            MultipartFormEnd;
+
+        private const string MultipartFormWithSpecialCharacters =
+            MultipartFormFileSpecialCharacters +
+            MultipartFormEndWithSpecialCharacters;
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadForm_EmptyMultipart_ReturnsParsedFormCollection(bool bufferRequest)
+        {
+            var formContent = Encoding.UTF8.GetBytes(EmptyMultipartForm);
+            var context = new DefaultHttpContext();
+            var responseFeature = new FakeResponseFeature();
+            context.Features.Set<IHttpResponseFeature>(responseFeature);
+            context.Request.ContentType = MultipartContentType;
+            context.Request.Body = new NonSeekableReadStream(formContent);
+
+            IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
+            context.Features.Set<IFormFeature>(formFeature);
+
+            var formCollection = context.Request.Form;
+
+            Assert.NotNull(formCollection);
+
+            // Cached
+            formFeature = context.Features.Get<IFormFeature>();
+            Assert.NotNull(formFeature);
+            Assert.NotNull(formFeature.Form);
+            Assert.Same(formCollection, formFeature.Form);
+            Assert.Same(formCollection, await context.Request.ReadFormAsync());
+
+            // Content
+            Assert.Equal(0, formCollection.Count);
+            Assert.NotNull(formCollection.Files);
+            Assert.Equal(0, formCollection.Files.Count);
+
+            // Cleanup
+            await responseFeature.CompleteAsync();
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadForm_MultipartWithField_ReturnsParsedFormCollection(bool bufferRequest)
+        {
+            var formContent = Encoding.UTF8.GetBytes(MultipartFormWithField);
+            var context = new DefaultHttpContext();
+            var responseFeature = new FakeResponseFeature();
+            context.Features.Set<IHttpResponseFeature>(responseFeature);
+            context.Request.ContentType = MultipartContentType;
+            context.Request.Body = new NonSeekableReadStream(formContent);
+
+            IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
+            context.Features.Set<IFormFeature>(formFeature);
+
+            var formCollection = context.Request.Form;
+
+            Assert.NotNull(formCollection);
+
+            // Cached
+            formFeature = context.Features.Get<IFormFeature>();
+            Assert.NotNull(formFeature);
+            Assert.NotNull(formFeature.Form);
+            Assert.Same(formCollection, formFeature.Form);
+            Assert.Same(formCollection, await context.Request.ReadFormAsync());
+
+            // Content
+            Assert.Equal(1, formCollection.Count);
+            Assert.Equal("Foo", formCollection["description"]);
+
+            Assert.NotNull(formCollection.Files);
+            Assert.Equal(0, formCollection.Files.Count);
+
+            // Cleanup
+            await responseFeature.CompleteAsync();
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_MultipartWithFile_ReturnsParsedFormCollection(bool bufferRequest)
+        {
+            var formContent = Encoding.UTF8.GetBytes(MultipartFormWithFile);
+            var context = new DefaultHttpContext();
+            var responseFeature = new FakeResponseFeature();
+            context.Features.Set<IHttpResponseFeature>(responseFeature);
+            context.Request.ContentType = MultipartContentType;
+            context.Request.Body = new NonSeekableReadStream(formContent);
+
+            IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
+            context.Features.Set<IFormFeature>(formFeature);
+
+            var formCollection = await context.Request.ReadFormAsync();
+
+            Assert.NotNull(formCollection);
+
+            // Cached
+            formFeature = context.Features.Get<IFormFeature>();
+            Assert.NotNull(formFeature);
+            Assert.NotNull(formFeature.Form);
+            Assert.Same(formFeature.Form, formCollection);
+            Assert.Same(formCollection, context.Request.Form);
+
+            // Content
+            Assert.Equal(0, formCollection.Count);
+
+            Assert.NotNull(formCollection.Files);
+            Assert.Equal(1, formCollection.Files.Count);
+
+            var file = formCollection.Files["myfile1"];
+            Assert.Equal("myfile1", file.Name);
+            Assert.Equal("temp.html", file.FileName);
+            Assert.Equal("text/html", file.ContentType);
+            Assert.Equal(@"form-data; name=""myfile1""; filename=""temp.html""", file.ContentDisposition);
+            var body = file.OpenReadStream();
+            using (var reader = new StreamReader(body))
+            {
+                Assert.True(body.CanSeek);
+                var content = reader.ReadToEnd();
+                Assert.Equal("<html><body>Hello World</body></html>", content);
+            }
+
+            await responseFeature.CompleteAsync();
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_MultipartWithFileAndQuotedBoundaryString_ReturnsParsedFormCollection(bool bufferRequest)
+        {
+            var formContent = Encoding.UTF8.GetBytes(MultipartFormWithSpecialCharacters);
+            var context = new DefaultHttpContext();
+            var responseFeature = new FakeResponseFeature();
+            context.Features.Set<IHttpResponseFeature>(responseFeature);
+            context.Request.ContentType = MultipartContentTypeWithSpecialCharacters;
+            context.Request.Body = new NonSeekableReadStream(formContent);
+
+            IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
+            context.Features.Set<IFormFeature>(formFeature);
+
+            var formCollection = context.Request.Form;
+
+            Assert.NotNull(formCollection);
+
+            // Cached
+            formFeature = context.Features.Get<IFormFeature>();
+            Assert.NotNull(formFeature);
+            Assert.NotNull(formFeature.Form);
+            Assert.Same(formCollection, formFeature.Form);
+            Assert.Same(formCollection, await context.Request.ReadFormAsync());
+
+            // Content
+            Assert.Equal(1, formCollection.Count);
+            Assert.Equal("Foo", formCollection["description"]);
+
+            Assert.NotNull(formCollection.Files);
+            Assert.Equal(0, formCollection.Files.Count);
+
+            // Cleanup
+            await responseFeature.CompleteAsync();
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_MultipartWithEncodedFilename_ReturnsParsedFormCollection(bool bufferRequest)
+        {
+            var formContent = Encoding.UTF8.GetBytes(MultipartFormWithEncodedFilename);
+            var context = new DefaultHttpContext();
+            var responseFeature = new FakeResponseFeature();
+            context.Features.Set<IHttpResponseFeature>(responseFeature);
+            context.Request.ContentType = MultipartContentType;
+            context.Request.Body = new NonSeekableReadStream(formContent);
+
+            IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
+            context.Features.Set<IFormFeature>(formFeature);
+
+            var formCollection = await context.Request.ReadFormAsync();
+
+            Assert.NotNull(formCollection);
+
+            // Cached
+            formFeature = context.Features.Get<IFormFeature>();
+            Assert.NotNull(formFeature);
+            Assert.NotNull(formFeature.Form);
+            Assert.Same(formFeature.Form, formCollection);
+            Assert.Same(formCollection, context.Request.Form);
+
+            // Content
+            Assert.Equal(0, formCollection.Count);
+
+            Assert.NotNull(formCollection.Files);
+            Assert.Equal(1, formCollection.Files.Count);
+
+            var file = formCollection.Files["myfile1"];
+            Assert.Equal("myfile1", file.Name);
+            Assert.Equal("t\u00e9mp.html", file.FileName);
+            Assert.Equal("text/html", file.ContentType);
+            Assert.Equal(@"form-data; name=""myfile1""; filename=""temp.html""; filename*=utf-8''t%c3%a9mp.html", file.ContentDisposition);
+            var body = file.OpenReadStream();
+            using (var reader = new StreamReader(body))
+            {
+                Assert.True(body.CanSeek);
+                var content = reader.ReadToEnd();
+                Assert.Equal("<html><body>Hello World</body></html>", content);
+            }
+
+            await responseFeature.CompleteAsync();
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_MultipartWithFieldAndFile_ReturnsParsedFormCollection(bool bufferRequest)
+        {
+            var formContent = Encoding.UTF8.GetBytes(MultipartFormWithFieldAndFile);
+            var context = new DefaultHttpContext();
+            var responseFeature = new FakeResponseFeature();
+            context.Features.Set<IHttpResponseFeature>(responseFeature);
+            context.Request.ContentType = MultipartContentType;
+            context.Request.Body = new NonSeekableReadStream(formContent);
+
+            IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
+            context.Features.Set<IFormFeature>(formFeature);
+
+            var formCollection = await context.Request.ReadFormAsync();
+
+            Assert.NotNull(formCollection);
+
+            // Cached
+            formFeature = context.Features.Get<IFormFeature>();
+            Assert.NotNull(formFeature);
+            Assert.NotNull(formFeature.Form);
+            Assert.Same(formFeature.Form, formCollection);
+            Assert.Same(formCollection, context.Request.Form);
+
+            // Content
+            Assert.Equal(1, formCollection.Count);
+            Assert.Equal("Foo", formCollection["description"]);
+
+            Assert.NotNull(formCollection.Files);
+            Assert.Equal(1, formCollection.Files.Count);
+
+            var file = formCollection.Files["myfile1"];
+            Assert.Equal("text/html", file.ContentType);
+            Assert.Equal(@"form-data; name=""myfile1""; filename=""temp.html""", file.ContentDisposition);
+            var body = file.OpenReadStream();
+            using (var reader = new StreamReader(body))
+            {
+                Assert.True(body.CanSeek);
+                var content = reader.ReadToEnd();
+                Assert.Equal("<html><body>Hello World</body></html>", content);
+            }
+
+            await responseFeature.CompleteAsync();
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_ValueCountLimitExceeded_Throw(bool bufferRequest)
+        {
+            var formContent = new List<byte>();
+            formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormField));
+            formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormField));
+            formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormField));
+            formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormEnd));
+
+            var context = new DefaultHttpContext();
+            var responseFeature = new FakeResponseFeature();
+            context.Features.Set<IHttpResponseFeature>(responseFeature);
+            context.Request.ContentType = MultipartContentType;
+            context.Request.Body = new NonSeekableReadStream(formContent.ToArray());
+
+            IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest, ValueCountLimit = 2 });
+            context.Features.Set<IFormFeature>(formFeature);
+
+            var exception = await Assert.ThrowsAsync<InvalidDataException> (() => context.Request.ReadFormAsync());
+            Assert.Equal("Form value count limit 2 exceeded.", exception.Message);
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_ValueCountLimitExceededWithFiles_Throw(bool bufferRequest)
+        {
+            var formContent = new List<byte>();
+            formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFile));
+            formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFile));
+            formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormFile));
+            formContent.AddRange(Encoding.UTF8.GetBytes(MultipartFormEnd));
+
+
+            var context = new DefaultHttpContext();
+            var responseFeature = new FakeResponseFeature();
+            context.Features.Set<IHttpResponseFeature>(responseFeature);
+            context.Request.ContentType = MultipartContentType;
+            context.Request.Body = new NonSeekableReadStream(formContent.ToArray());
+
+            IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest, ValueCountLimit = 2 });
+            context.Features.Set<IFormFeature>(formFeature);
+
+            var exception = await Assert.ThrowsAsync<InvalidDataException> (() => context.Request.ReadFormAsync());
+            Assert.Equal("Form value count limit 2 exceeded.", exception.Message);
+        }
+
+        [Theory]
+        // FileBufferingReadStream transitions to disk storage after 30kb, and stops pooling buffers at 1mb.
+        [InlineData(true, 1024)]
+        [InlineData(false, 1024)]
+        [InlineData(true, 40 * 1024)]
+        [InlineData(false, 40 * 1024)]
+        [InlineData(true, 4 * 1024 * 1024)]
+        [InlineData(false, 4 * 1024 * 1024)]
+        public async Task ReadFormAsync_MultipartWithFieldAndMediumFile_ReturnsParsedFormCollection(bool bufferRequest, int fileSize)
+        {
+            var fileContents = CreateFile(fileSize);
+            var formContent = CreateMultipartWithFormAndFile(fileContents);
+            var context = new DefaultHttpContext();
+            var responseFeature = new FakeResponseFeature();
+            context.Features.Set<IHttpResponseFeature>(responseFeature);
+            context.Request.ContentType = MultipartContentType;
+            context.Request.Body = new NonSeekableReadStream(formContent);
+
+            IFormFeature formFeature = new FormFeature(context.Request, new FormOptions() { BufferBody = bufferRequest });
+            context.Features.Set<IFormFeature>(formFeature);
+
+            var formCollection = await context.Request.ReadFormAsync();
+
+            Assert.NotNull(formCollection);
+
+            // Cached
+            formFeature = context.Features.Get<IFormFeature>();
+            Assert.NotNull(formFeature);
+            Assert.NotNull(formFeature.Form);
+            Assert.Same(formFeature.Form, formCollection);
+            Assert.Same(formCollection, context.Request.Form);
+
+            // Content
+            Assert.Equal(1, formCollection.Count);
+            Assert.Equal("Foo", formCollection["description"]);
+
+            Assert.NotNull(formCollection.Files);
+            Assert.Equal(1, formCollection.Files.Count);
+
+            var file = formCollection.Files["myfile1"];
+            Assert.Equal("text/html", file.ContentType);
+            Assert.Equal(@"form-data; name=""myfile1""; filename=""temp.html""", file.ContentDisposition);
+            using (var body = file.OpenReadStream())
+            {
+                Assert.True(body.CanSeek);
+                CompareStreams(fileContents, body);
+            }
+
+            await responseFeature.CompleteAsync();
+        }
+
+        private Stream CreateFile(int size)
+        {
+            var stream = new MemoryStream(size);
+            var bytes = Encoding.ASCII.GetBytes("HelloWorld_ABCDEFGHIJKLMNOPQRSTUVWXYZ.abcdefghijklmnopqrstuvwxyz,0123456789;");
+            int written = 0;
+            while (written < size)
+            {
+                var toWrite = Math.Min(size - written, bytes.Length);
+                stream.Write(bytes, 0, toWrite);
+                written += toWrite;
+            }
+            stream.Position = 0;
+            return stream;
+        }
+
+        private Stream CreateMultipartWithFormAndFile(Stream fileContents)
+        {
+            var stream = new MemoryStream();
+            var header =
+MultipartFormField +
+"--WebKitFormBoundary5pDRpGheQXaM8k3T\r\n" +
+"Content-Disposition: form-data; name=\"myfile1\"; filename=\"temp.html\"\r\n" +
+"Content-Type: text/html\r\n" +
+"\r\n";
+            var footer =
+"\r\n--WebKitFormBoundary5pDRpGheQXaM8k3T--";
+
+            var bytes = Encoding.ASCII.GetBytes(header);
+            stream.Write(bytes, 0, bytes.Length);
+
+            fileContents.CopyTo(stream);
+            fileContents.Position = 0;
+
+            bytes = Encoding.ASCII.GetBytes(footer);
+            stream.Write(bytes, 0, bytes.Length);
+            stream.Position = 0;
+            return stream;
+        }
+
+        private void CompareStreams(Stream streamA, Stream streamB)
+        {
+            Assert.Equal(streamA.Length, streamB.Length);
+            byte[] bytesA = new byte[1024], bytesB = new byte[1024];
+            var readA = streamA.Read(bytesA, 0, bytesA.Length);
+            var readB = streamB.Read(bytesB, 0, bytesB.Length);
+            Assert.Equal(readA, readB);
+            var loops = 0;
+            while (readA > 0)
+            {
+                for (int i = 0; i < readA; i++)
+                {
+                    if (bytesA[i] != bytesB[i])
+                    {
+                        throw new Exception($"Value mismatch at loop {loops}, index {i}; A:{bytesA[i]}, B:{bytesB[i]}");
+                    }
+                }
+
+                readA = streamA.Read(bytesA, 0, bytesA.Length);
+                readB = streamB.Read(bytesB, 0, bytesB.Length);
+                Assert.Equal(readA, readB);
+                loops++;
+            }
+        }
+    }
+}
diff --git a/src/Http/Http/test/Features/HttpRequestIdentifierFeatureTests.cs b/src/Http/Http/test/Features/HttpRequestIdentifierFeatureTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7b17028cdfdfbf28a29164f541cab7f127d465a7
--- /dev/null
+++ b/src/Http/Http/test/Features/HttpRequestIdentifierFeatureTests.cs
@@ -0,0 +1,43 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class HttpRequestIdentifierFeatureTests
+    {
+        [Fact]
+        public void TraceIdentifier_ReturnsId()
+        {
+            var feature = new HttpRequestIdentifierFeature();
+
+            var id = feature.TraceIdentifier;
+
+            Assert.NotNull(id);
+        }
+
+        [Fact]
+        public void TraceIdentifier_ReturnsStableId()
+        {
+            var feature = new HttpRequestIdentifierFeature();
+
+            var id1 = feature.TraceIdentifier;
+            var id2 = feature.TraceIdentifier;
+
+            Assert.Equal(id1, id2);
+        }
+
+        [Fact]
+        public void TraceIdentifier_ReturnsUniqueIdForDifferentInstances()
+        {
+            var feature1 = new HttpRequestIdentifierFeature();
+            var feature2 = new HttpRequestIdentifierFeature();
+
+            var id1 = feature1.TraceIdentifier;
+            var id2 = feature2.TraceIdentifier;
+
+            Assert.NotEqual(id1, id2);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/test/Features/NonSeekableReadStream.cs b/src/Http/Http/test/Features/NonSeekableReadStream.cs
new file mode 100644
index 0000000000000000000000000000000000000000..279da7992b94f8d66312211130af1680cc5b4d19
--- /dev/null
+++ b/src/Http/Http/test/Features/NonSeekableReadStream.cs
@@ -0,0 +1,72 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class NonSeekableReadStream : Stream
+    {
+        private Stream _inner;
+
+        public NonSeekableReadStream(byte[] data)
+            : this(new MemoryStream(data))
+        {
+        }
+
+        public NonSeekableReadStream(Stream inner)
+        {
+            _inner = inner;
+        }
+
+        public override bool CanRead => _inner.CanRead;
+
+        public override bool CanSeek => false;
+
+        public override bool CanWrite => false;
+
+        public override long Length
+        {
+            get { throw new NotSupportedException(); }
+        }
+
+        public override long Position
+        {
+            get { throw new NotSupportedException(); }
+            set { throw new NotSupportedException(); }
+        }
+
+        public override void Flush()
+        {
+            throw new NotImplementedException();
+        }
+
+        public override long Seek(long offset, SeekOrigin origin)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override void SetLength(long value)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override void Write(byte[] buffer, int offset, int count)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            return _inner.Read(buffer, offset, count);
+        }
+
+        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            return _inner.ReadAsync(buffer, offset, count, cancellationToken);
+        }
+    }
+}
diff --git a/src/Http/Http/test/Features/QueryFeatureTests.cs b/src/Http/Http/test/Features/QueryFeatureTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e43e3ce7a976bdddcf5aad9a419a13792ff7a9f1
--- /dev/null
+++ b/src/Http/Http/test/Features/QueryFeatureTests.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 Xunit;
+
+namespace Microsoft.AspNetCore.Http.Features
+{
+    public class QueryFeatureTests
+    {
+        [Fact]
+        public void QueryReturnsParsedQueryCollection()
+        {
+            // Arrange
+            var features = new FeatureCollection();
+            var request = new HttpRequestFeature();
+            request.QueryString = "foo=bar";
+            features[typeof(IHttpRequestFeature)] = request;
+
+            var provider = new QueryFeature(features);
+
+            // Act
+            var queryCollection = provider.Query;
+
+            // Assert
+            Assert.Equal("bar", queryCollection["foo"]);
+        }
+
+        [Theory]
+        [InlineData("?q", "q")]
+        [InlineData("?q&", "q")]
+        [InlineData("?q1=abc&q2", "q2")]
+        [InlineData("?q=", "q")]
+        [InlineData("?q=&", "q")]
+        public void KeyWithoutValuesAddedToQueryCollection(string queryString, string emptyParam)
+        {
+            var features = new FeatureCollection();
+            var request = new HttpRequestFeature();
+            request.QueryString = queryString;
+            features[typeof(IHttpRequestFeature)] = request;
+
+            var provider = new QueryFeature(features);
+
+            var queryCollection = provider.Query;
+
+            Assert.True(queryCollection.Keys.Contains(emptyParam));
+            Assert.Equal(string.Empty, queryCollection[emptyParam]);
+        }
+
+        [Theory]
+        [InlineData("?&&")]
+        [InlineData("?&")]
+        [InlineData("&&")]
+        public void EmptyKeysNotAddedToQueryCollection(string queryString)
+        {
+            var features = new FeatureCollection();
+            var request = new HttpRequestFeature();
+            request.QueryString = queryString;
+            features[typeof(IHttpRequestFeature)] = request;
+
+            var provider = new QueryFeature(features);
+
+            var queryCollection = provider.Query;
+
+            Assert.Equal(0, queryCollection.Count);
+        }
+    }
+}
diff --git a/src/Http/Http/test/HeaderDictionaryTests.cs b/src/Http/Http/test/HeaderDictionaryTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..03d642a0181dc52d8b03023c49dd2a38b025d3ca
--- /dev/null
+++ b/src/Http/Http/test/HeaderDictionaryTests.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.Collections.Generic;
+using System.Linq;
+using Microsoft.Extensions.Primitives;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public class HeaderDictionaryTests
+    {
+        public static TheoryData HeaderSegmentData => new TheoryData<IEnumerable<string>>
+        {
+          new[] { "Value1", "Value2", "Value3", "Value4" },
+          new[] { "Value1", "", "Value3", "Value4" },
+          new[] { "Value1", "", "", "Value4" },
+          new[] { "Value1", "", null, "Value4" },
+          new[] { "", "", "", "" },
+          new[] { "", null, "", null },
+        };
+
+        [Fact]
+        public void PropertiesAreAccessible()
+        {
+            var headers = new HeaderDictionary(
+                new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase)
+                {
+                    { "Header1", "Value1" }
+                });
+
+            Assert.Single(headers);
+            Assert.Equal<string>(new[] { "Header1" }, headers.Keys);
+            Assert.True(headers.ContainsKey("header1"));
+            Assert.False(headers.ContainsKey("header2"));
+            Assert.Equal("Value1", headers["header1"]);
+            Assert.Equal(new[] { "Value1" }, headers["header1"].ToArray());
+        }
+
+        [Theory]
+        [MemberData(nameof(HeaderSegmentData))]
+        public void EmptyHeaderSegmentsAreIgnored(IEnumerable<string> segments)
+        {
+            var header = string.Join(",", segments);
+
+            var headers = new HeaderDictionary(
+               new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase)
+               {
+                    { "Header1",  header},
+               });
+
+            var result = headers.GetCommaSeparatedValues("Header1");
+            var expectedResult = segments.Where(s => !string.IsNullOrEmpty(s));
+
+            Assert.Equal(expectedResult, result);
+        }
+
+        [Fact]
+        public void EmtpyQuotedHeaderSegmentsAreIgnored()
+        {
+            var headers = new HeaderDictionary(
+               new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase)
+               {
+                    { "Header1",  "Value1,\"\",,Value2" },
+               });
+
+            var result = headers.GetCommaSeparatedValues("Header1");
+            Assert.Equal(new[] { "Value1", "Value2" }, result);
+        }
+
+        [Fact]
+        public void ReadActionsWorkWhenReadOnly()
+        {
+            var headers = new HeaderDictionary(
+                new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase)
+                {
+                    { "Header1", "Value1" }
+                });
+
+            headers.IsReadOnly = true;
+
+            Assert.Single(headers);
+            Assert.Equal<string>(new[] { "Header1" }, headers.Keys);
+            Assert.True(headers.ContainsKey("header1"));
+            Assert.False(headers.ContainsKey("header2"));
+            Assert.Equal("Value1", headers["header1"]);
+            Assert.Equal(new[] { "Value1" }, headers["header1"].ToArray());
+        }
+
+        [Fact]
+        public void WriteActionsThrowWhenReadOnly()
+        {
+            var headers = new HeaderDictionary();
+            headers.IsReadOnly = true;
+
+            Assert.Throws<InvalidOperationException>(() => headers["header1"] = "value1");
+            Assert.Throws<InvalidOperationException>(() => ((IDictionary<string, StringValues>)headers)["header1"] = "value1");
+            Assert.Throws<InvalidOperationException>(() => headers.ContentLength = 12);
+            Assert.Throws<InvalidOperationException>(() => headers.Add(new KeyValuePair<string, StringValues>("header1", "value1")));
+            Assert.Throws<InvalidOperationException>(() => headers.Add("header1", "value1"));
+            Assert.Throws<InvalidOperationException>(() => headers.Clear());
+            Assert.Throws<InvalidOperationException>(() => headers.Remove(new KeyValuePair<string, StringValues>("header1", "value1")));
+            Assert.Throws<InvalidOperationException>(() => headers.Remove("header1"));
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/test/HttpContextFactoryTests.cs b/src/Http/Http/test/HttpContextFactoryTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..ba983198e75eccbff1f091cc5df3f4c8e5fbe150
--- /dev/null
+++ b/src/Http/Http/test/HttpContextFactoryTests.cs
@@ -0,0 +1,39 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http
+{
+    public class HttpContextFactoryTests
+    {
+        [Fact]
+        public void CreateHttpContextSetsHttpContextAccessor()
+        {
+            // Arrange
+            var accessor = new HttpContextAccessor();
+            var contextFactory = new HttpContextFactory(Options.Create(new FormOptions()), accessor);
+
+            // Act
+            var context = contextFactory.Create(new FeatureCollection());
+
+            // Assert
+            Assert.True(ReferenceEquals(context, accessor.HttpContext));
+        }
+
+        [Fact]
+        public void AllowsCreatingContextWithoutSettingAccessor()
+        {
+            // Arrange
+            var contextFactory = new HttpContextFactory(Options.Create(new FormOptions()));
+
+            // Act & Assert
+            var context = contextFactory.Create(new FeatureCollection());
+            contextFactory.Dispose(context);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/test/HttpServiceCollectionExtensionsTests.cs b/src/Http/Http/test/HttpServiceCollectionExtensionsTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a317e993460b72cb631a68be5a536179febb71ea
--- /dev/null
+++ b/src/Http/Http/test/HttpServiceCollectionExtensionsTests.cs
@@ -0,0 +1,33 @@
+// 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.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Tests
+{
+    public class HttpServiceCollectionExtensionsTests
+    {
+        [Fact]
+        public void AddHttpContextAccessor_AddsWithCorrectLifetime()
+        {
+            // Arrange
+            var services = new ServiceCollection();
+
+            // Act
+            services.AddHttpContextAccessor();
+
+            // Assert
+            var descriptor = services[0];
+            Assert.Equal(ServiceLifetime.Singleton, descriptor.Lifetime);
+            Assert.Equal(typeof(HttpContextAccessor), descriptor.ImplementationType);
+        }
+
+        [Fact]
+        public void AddHttpContextAccessor_ThrowsWithoutServices()
+        {
+            Assert.Throws<ArgumentNullException>("services", () => HttpServiceCollectionExtensions.AddHttpContextAccessor(null));
+        }
+    }
+}
diff --git a/src/Http/Http/test/Internal/ApplicationBuilderTests.cs b/src/Http/Http/test/Internal/ApplicationBuilderTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e1336c82ba09e5198aa1f6fed740e73531a453c9
--- /dev/null
+++ b/src/Http/Http/test/Internal/ApplicationBuilderTests.cs
@@ -0,0 +1,35 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Http;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Builder.Internal
+{
+    public class ApplicationBuilderTests
+    {
+        [Fact]
+        public void BuildReturnsCallableDelegate()
+        {
+            var builder = new ApplicationBuilder(null);
+            var app = builder.Build();
+
+            var httpContext = new DefaultHttpContext();
+
+            app.Invoke(httpContext);
+            Assert.Equal(404, httpContext.Response.StatusCode);
+        }
+
+        [Fact]
+        public void PropertiesDictionaryIsDistinctAfterNew()
+        {
+            var builder1 = new ApplicationBuilder(null);
+            builder1.Properties["test"] = "value1";
+
+            var builder2 = builder1.New();
+            builder2.Properties["test"] = "value2";
+
+            Assert.Equal("value1", builder1.Properties["test"]);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/test/Internal/BindingAddressTests.cs b/src/Http/Http/test/Internal/BindingAddressTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3bca310e8239931a766c2eb52fa815d5731042f8
--- /dev/null
+++ b/src/Http/Http/test/Internal/BindingAddressTests.cs
@@ -0,0 +1,70 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xunit;
+namespace Microsoft.AspNetCore.Http.Internal.Tests
+{
+    public class BindingAddressTests
+    {
+        [Theory]
+        [InlineData("")]
+        [InlineData("5000")]
+        [InlineData("//noscheme")]
+        public void FromUriThrowsForUrlsWithoutSchemeDelimiter(string url)
+        {
+            Assert.Throws<FormatException>(() => BindingAddress.Parse(url));
+        }
+
+        [Theory]
+        [InlineData("://")]
+        [InlineData("://:5000")]
+        [InlineData("http://")]
+        [InlineData("http://:5000")]
+        [InlineData("http:///")]
+        [InlineData("http:///:5000")]
+        [InlineData("http:////")]
+        [InlineData("http:////:5000")]
+        public void FromUriThrowsForUrlsWithoutHost(string url)
+        {
+            Assert.Throws<FormatException>(() => BindingAddress.Parse(url));
+        }
+
+        [Theory]
+        [InlineData("://emptyscheme", "", "emptyscheme", 0, "", "://emptyscheme:0")]
+        [InlineData("http://+", "http", "+", 80, "", "http://+:80")]
+        [InlineData("http://*", "http", "*", 80, "", "http://*:80")]
+        [InlineData("http://localhost", "http", "localhost", 80, "", "http://localhost:80")]
+        [InlineData("http://www.example.com", "http", "www.example.com", 80, "", "http://www.example.com:80")]
+        [InlineData("https://www.example.com", "https", "www.example.com", 443, "", "https://www.example.com:443")]
+        [InlineData("http://www.example.com/", "http", "www.example.com", 80, "", "http://www.example.com:80")]
+        [InlineData("http://www.example.com/foo?bar=baz", "http", "www.example.com", 80, "/foo?bar=baz", "http://www.example.com:80/foo?bar=baz")]
+        [InlineData("http://www.example.com:5000", "http", "www.example.com", 5000, "", null)]
+        [InlineData("https://www.example.com:5000", "https", "www.example.com", 5000, "", null)]
+        [InlineData("http://www.example.com:5000/", "http", "www.example.com", 5000, "", "http://www.example.com:5000")]
+        [InlineData("http://www.example.com:NOTAPORT", "http", "www.example.com:NOTAPORT", 80, "", "http://www.example.com:notaport:80")]
+        [InlineData("https://www.example.com:NOTAPORT", "https", "www.example.com:NOTAPORT", 443, "", "https://www.example.com:notaport:443")]
+        [InlineData("http://www.example.com:NOTAPORT/", "http", "www.example.com:NOTAPORT", 80, "", "http://www.example.com:notaport:80")]
+        [InlineData("http://foo:/tmp/kestrel-test.sock:5000/doesn't/matter", "http", "foo:", 80, "/tmp/kestrel-test.sock:5000/doesn't/matter", "http://foo::80/tmp/kestrel-test.sock:5000/doesn't/matter")]
+        [InlineData("http://unix:foo/tmp/kestrel-test.sock", "http", "unix:foo", 80, "/tmp/kestrel-test.sock", "http://unix:foo:80/tmp/kestrel-test.sock")]
+        [InlineData("http://unix:5000/tmp/kestrel-test.sock", "http", "unix", 5000, "/tmp/kestrel-test.sock", "http://unix:5000/tmp/kestrel-test.sock")]
+        [InlineData("http://unix:/tmp/kestrel-test.sock", "http", "unix:/tmp/kestrel-test.sock", 0, "", null)]
+        [InlineData("https://unix:/tmp/kestrel-test.sock", "https", "unix:/tmp/kestrel-test.sock", 0, "", null)]
+        [InlineData("http://unix:/tmp/kestrel-test.sock:", "http", "unix:/tmp/kestrel-test.sock", 0, "", "http://unix:/tmp/kestrel-test.sock")]
+        [InlineData("http://unix:/tmp/kestrel-test.sock:/", "http", "unix:/tmp/kestrel-test.sock", 0, "", "http://unix:/tmp/kestrel-test.sock")]
+        [InlineData("http://unix:/tmp/kestrel-test.sock:5000/doesn't/matter", "http", "unix:/tmp/kestrel-test.sock", 0, "5000/doesn't/matter", "http://unix:/tmp/kestrel-test.sock")]
+        public void UrlsAreParsedCorrectly(string url, string scheme, string host, int port, string pathBase, string toString)
+        {
+            var serverAddress = BindingAddress.Parse(url);
+
+            Assert.Equal(scheme, serverAddress.Scheme);
+            Assert.Equal(host, serverAddress.Host);
+            Assert.Equal(port, serverAddress.Port);
+            Assert.Equal(pathBase, serverAddress.PathBase);
+
+            Assert.Equal(toString ?? url, serverAddress.ToString());
+        }
+    }
+}
diff --git a/src/Http/Http/test/Internal/BufferingHelperTests.cs b/src/Http/Http/test/Internal/BufferingHelperTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9ad48986f501ac56384f76e93fb3cbaa0fc64ac6
--- /dev/null
+++ b/src/Http/Http/test/Internal/BufferingHelperTests.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.IO;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    public class BufferingHelperTests
+    {
+        [Fact]
+        public void GetTempDirectory_Returns_Valid_Location()
+        {
+            var tempDirectory = BufferingHelper.TempDirectory;
+            Assert.NotNull(tempDirectory);
+            Assert.True(Directory.Exists(tempDirectory));
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs b/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..dbe1d54dd0844ebd3c25bde45b688ec19d4e4943
--- /dev/null
+++ b/src/Http/Http/test/Internal/DefaultHttpRequestTests.cs
@@ -0,0 +1,235 @@
+// 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.Globalization;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Primitives;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    public class DefaultHttpRequestTests
+    {
+        [Theory]
+        [InlineData(0)]
+        [InlineData(9001)]
+        [InlineData(65535)]
+        public void GetContentLength_ReturnsParsedHeader(long value)
+        {
+            // Arrange
+            var request = GetRequestWithContentLength(value.ToString(CultureInfo.InvariantCulture));
+
+            // Act and Assert
+            Assert.Equal(value, request.ContentLength);
+        }
+
+        [Fact]
+        public void GetContentLength_ReturnsNullIfHeaderDoesNotExist()
+        {
+            // Arrange
+            var request = GetRequestWithContentLength(contentLength: null);
+
+            // Act and Assert
+            Assert.Null(request.ContentLength);
+        }
+
+        [Theory]
+        [InlineData("cant-parse-this")]
+        [InlineData("-1000")]
+        [InlineData("1000.00")]
+        [InlineData("100/5")]
+        public void GetContentLength_ReturnsNullIfHeaderCannotBeParsed(string contentLength)
+        {
+            // Arrange
+            var request = GetRequestWithContentLength(contentLength);
+
+            // Act and Assert
+            Assert.Null(request.ContentLength);
+        }
+
+        [Fact]
+        public void GetContentType_ReturnsNullIfHeaderDoesNotExist()
+        {
+            // Arrange
+            var request = GetRequestWithContentType(contentType: null);
+
+            // Act and Assert
+            Assert.Null(request.ContentType);
+        }
+
+        [Fact]
+        public void Host_GetsHostFromHeaders()
+        {
+            // Arrange
+            const string expected = "localhost:9001";
+
+            var headers = new HeaderDictionary()
+            {
+                { "Host", expected },
+            };
+
+            var request = CreateRequest(headers);
+
+            // Act
+            var host = request.Host;
+
+            // Assert
+            Assert.Equal(expected, host.Value);
+        }
+
+        [Fact]
+        public void Host_DecodesPunyCode()
+        {
+            // Arrange
+            const string expected = "löcalhöst";
+
+            var headers = new HeaderDictionary()
+            {
+                { "Host", "xn--lcalhst-90ae" },
+            };
+
+            var request = CreateRequest(headers);
+
+            // Act
+            var host = request.Host;
+
+            // Assert
+            Assert.Equal(expected, host.Value);
+        }
+
+        [Fact]
+        public void Host_EncodesPunyCode()
+        {
+            // Arrange
+            const string expected = "xn--lcalhst-90ae";
+
+            var headers = new HeaderDictionary();
+
+            var request = CreateRequest(headers);
+
+            // Act
+            request.Host = new HostString("löcalhöst");
+
+            // Assert
+            Assert.Equal(expected, headers["Host"][0]);
+        }
+
+        [Fact]
+        public void IsHttps_CorrectlyReflectsScheme()
+        {
+            var request = new DefaultHttpContext().Request;
+            Assert.Equal(string.Empty, request.Scheme);
+            Assert.False(request.IsHttps);
+            request.IsHttps = true;
+            Assert.Equal("https", request.Scheme);
+            request.IsHttps = false;
+            Assert.Equal("http", request.Scheme);
+            request.Scheme = "ftp";
+            Assert.False(request.IsHttps);
+            request.Scheme = "HTTPS";
+            Assert.True(request.IsHttps);
+        }
+
+        [Fact]
+        public void Query_GetAndSet()
+        {
+            var request = new DefaultHttpContext().Request;
+            var requestFeature = request.HttpContext.Features.Get<IHttpRequestFeature>();
+            Assert.Equal(string.Empty, requestFeature.QueryString);
+            Assert.Equal(QueryString.Empty, request.QueryString);
+            var query0 = request.Query;
+            Assert.NotNull(query0);
+            Assert.Equal(0, query0.Count);
+
+            requestFeature.QueryString = "?name0=value0&name1=value1";
+            var query1 = request.Query;
+            Assert.NotSame(query0, query1);
+            Assert.Equal(2, query1.Count);
+            Assert.Equal("value0", query1["name0"]);
+            Assert.Equal("value1", query1["name1"]);
+
+            var query2 = new QueryCollection( new Dictionary<string, StringValues>()
+            {
+                { "name2", "value2" }
+            });
+
+            request.Query = query2;
+            Assert.Same(query2, request.Query);
+            Assert.Equal("?name2=value2", requestFeature.QueryString);
+            Assert.Equal(new QueryString("?name2=value2"), request.QueryString);
+        }
+
+        [Fact]
+        public void Cookies_GetAndSet()
+        {
+            var request = new DefaultHttpContext().Request;
+            var cookieHeaders = request.Headers["Cookie"];
+            Assert.Empty(cookieHeaders);
+            var cookies0 = request.Cookies;
+            Assert.Empty(cookies0);
+            Assert.Null(cookies0["key0"]);
+            Assert.False(cookies0.ContainsKey("key0"));
+
+            var newCookies = new[] { "name0=value0%2C", "%5Ename1=value1" };
+            request.Headers["Cookie"] = newCookies;
+
+            cookies0 = RequestCookieCollection.Parse(newCookies);
+            var cookies1 = request.Cookies;
+            Assert.Equal(cookies0, cookies1);
+            Assert.Equal(2, cookies1.Count);
+            Assert.Equal("value0,", cookies1["name0"]);
+            Assert.Equal("value1", cookies1["^name1"]);
+            Assert.Equal(newCookies, request.Headers["Cookie"]);
+
+            var cookies2 = new RequestCookieCollection(new Dictionary<string,string>()
+            {
+                { "name2", "value2" }
+            });
+            request.Cookies = cookies2;
+            Assert.Equal(cookies2, request.Cookies);
+            Assert.Equal("value2", request.Cookies["name2"]);
+            cookieHeaders = request.Headers["Cookie"];
+            Assert.Equal(new[] { "name2=value2" }, cookieHeaders);
+        }
+
+        private static HttpRequest CreateRequest(IHeaderDictionary headers)
+        {
+            var context = new DefaultHttpContext();
+            context.Features.Get<IHttpRequestFeature>().Headers = headers;
+            return context.Request;
+        }
+
+        private static HttpRequest GetRequestWithContentLength(string contentLength = null)
+        {
+            return GetRequestWithHeader("Content-Length", contentLength);
+        }
+
+        private static HttpRequest GetRequestWithContentType(string contentType = null)
+        {
+            return GetRequestWithHeader("Content-Type", contentType);
+        }
+
+        private static HttpRequest GetRequestWithAcceptHeader(string acceptHeader = null)
+        {
+            return GetRequestWithHeader("Accept", acceptHeader);
+        }
+
+        private static HttpRequest GetRequestWithAcceptCharsetHeader(string acceptCharset = null)
+        {
+            return GetRequestWithHeader("Accept-Charset", acceptCharset);
+        }
+
+        private static HttpRequest GetRequestWithHeader(string headerName, string headerValue)
+        {
+            var headers = new HeaderDictionary();
+            if (headerValue != null)
+            {
+                headers.Add(headerName, headerValue);
+            }
+
+            return CreateRequest(headers);
+        }
+    }
+}
diff --git a/src/Http/Http/test/Internal/DefaultHttpResponseTests.cs b/src/Http/Http/test/Internal/DefaultHttpResponseTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4764c44a63e02fa8ddd89dcfb25387ce655f2bdf
--- /dev/null
+++ b/src/Http/Http/test/Internal/DefaultHttpResponseTests.cs
@@ -0,0 +1,90 @@
+// 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.Globalization;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.Primitives;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Internal
+{
+    public class DefaultHttpResponseTests
+    {
+        [Theory]
+        [InlineData(0)]
+        [InlineData(9001)]
+        [InlineData(65535)]
+        public void GetContentLength_ReturnsParsedHeader(long value)
+        {
+            // Arrange
+            var response = GetResponseWithContentLength(value.ToString(CultureInfo.InvariantCulture));
+
+            // Act and Assert
+            Assert.Equal(value, response.ContentLength);
+        }
+
+        [Fact]
+        public void GetContentLength_ReturnsNullIfHeaderDoesNotExist()
+        {
+            // Arrange
+            var response = GetResponseWithContentLength(contentLength: null);
+
+            // Act and Assert
+            Assert.Null(response.ContentLength);
+        }
+
+        [Theory]
+        [InlineData("cant-parse-this")]
+        [InlineData("-1000")]
+        [InlineData("1000.00")]
+        [InlineData("100/5")]
+        public void GetContentLength_ReturnsNullIfHeaderCannotBeParsed(string contentLength)
+        {
+            // Arrange
+            var response = GetResponseWithContentLength(contentLength);
+
+            // Act and Assert
+            Assert.Null(response.ContentLength);
+        }
+
+        [Fact]
+        public void GetContentType_ReturnsNullIfHeaderDoesNotExist()
+        {
+            // Arrange
+            var response = GetResponseWithContentType(contentType: null);
+
+            // Act and Assert
+            Assert.Null(response.ContentType);
+        }
+
+        private static HttpResponse CreateResponse(IHeaderDictionary headers)
+        {
+            var context = new DefaultHttpContext();
+            context.Features.Get<IHttpResponseFeature>().Headers = headers;
+            return context.Response;
+        }
+
+        private static HttpResponse GetResponseWithContentLength(string contentLength = null)
+        {
+            return GetResponseWithHeader("Content-Length", contentLength);
+        }
+
+        private static HttpResponse GetResponseWithContentType(string contentType = null)
+        {
+            return GetResponseWithHeader("Content-Type", contentType);
+        }
+
+        private static HttpResponse GetResponseWithHeader(string headerName, string headerValue)
+        {
+            var headers = new HeaderDictionary();
+            if (headerValue != null)
+            {
+                headers.Add(headerName, headerValue);
+            }
+
+            return CreateResponse(headers);
+        }
+    }
+}
diff --git a/src/Http/Http/test/Microsoft.AspNetCore.Http.Tests.csproj b/src/Http/Http/test/Microsoft.AspNetCore.Http.Tests.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..c072fc6f67ba26c14d573fc57db869d35cf00861
--- /dev/null
+++ b/src/Http/Http/test/Microsoft.AspNetCore.Http.Tests.csproj
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Http" />
+    <Reference Include="Microsoft.Extensions.DependencyInjection" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/Http/test/RequestCookiesCollectionTests.cs b/src/Http/Http/test/RequestCookiesCollectionTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..70106df027e721919b7841db8e59f7fd5187eda8
--- /dev/null
+++ b/src/Http/Http/test/RequestCookiesCollectionTests.cs
@@ -0,0 +1,43 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Linq;
+using Microsoft.AspNetCore.Http.Internal;
+using Microsoft.Extensions.Primitives;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Tests
+{
+    public class RequestCookiesCollectionTests
+    {
+        public static TheoryData UnEscapesKeyValues_Data
+        {
+            get
+            {
+                // key, value, expected
+                return new TheoryData<string, string, string>
+                {
+                    { "key=value", "key", "value" },
+                    { "key%2C=%21value", "key,", "!value" },
+                    { "ke%23y%2C=val%5Eue", "ke#y,", "val^ue" },
+                    { "base64=QUI%2BREU%2FRw%3D%3D", "base64", "QUI+REU/Rw==" },
+                    { "base64=QUI+REU/Rw==", "base64", "QUI+REU/Rw==" },
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(UnEscapesKeyValues_Data))]
+        public void UnEscapesKeyValues(
+            string input,
+            string expectedKey,
+            string expectedValue)
+        {
+            var cookies = RequestCookieCollection.Parse(new StringValues(input));
+
+            Assert.Equal(1, cookies.Count);
+            Assert.Equal(expectedKey, cookies.Keys.Single());
+            Assert.Equal(expectedValue, cookies[expectedKey]);
+        }
+    }
+}
diff --git a/src/Http/Http/test/ResponseCookiesTest.cs b/src/Http/Http/test/ResponseCookiesTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5e5c44f89d09f6c4c2771e954140b8c72dd5a5b3
--- /dev/null
+++ b/src/Http/Http/test/ResponseCookiesTest.cs
@@ -0,0 +1,124 @@
+// 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.Http.Internal;
+using Microsoft.Net.Http.Headers;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Http.Tests
+{
+    public class ResponseCookiesTest
+    {
+        [Fact]
+        public void DeleteCookieShouldSetDefaultPath()
+        {
+            var headers = new HeaderDictionary();
+            var cookies = new ResponseCookies(headers, null);
+            var testcookie = "TestCookie";
+
+            cookies.Delete(testcookie);
+
+            var cookieHeaderValues = headers[HeaderNames.SetCookie];
+            Assert.Single(cookieHeaderValues);
+            Assert.StartsWith(testcookie, cookieHeaderValues[0]);
+            Assert.Contains("path=/", cookieHeaderValues[0]);
+            Assert.Contains("expires=Thu, 01 Jan 1970 00:00:00 GMT", cookieHeaderValues[0]);
+        }
+
+        [Fact]
+        public void DeleteCookieWithCookieOptionsShouldKeepPropertiesOfCookieOptions()
+        {
+            var headers = new HeaderDictionary();
+            var cookies = new ResponseCookies(headers, null);
+            var testcookie = "TestCookie";
+            var time = new DateTimeOffset(2000, 1, 1, 1, 1, 1, 1, TimeSpan.Zero);
+            var options = new CookieOptions
+            {
+                Secure = true,
+                HttpOnly = true,
+                Path = "/",
+                Expires = time,
+                Domain = "example.com",
+                SameSite = SameSiteMode.Lax
+            };
+
+            cookies.Delete(testcookie, options);
+
+            var cookieHeaderValues = headers[HeaderNames.SetCookie];
+            Assert.Single(cookieHeaderValues);
+            Assert.StartsWith(testcookie, cookieHeaderValues[0]);
+            Assert.Contains("path=/", cookieHeaderValues[0]);
+            Assert.Contains("expires=Thu, 01 Jan 1970 00:00:00 GMT", cookieHeaderValues[0]);
+            Assert.Contains("secure", cookieHeaderValues[0]);
+            Assert.Contains("httponly", cookieHeaderValues[0]);
+            Assert.Contains("samesite", cookieHeaderValues[0]);
+        }
+
+        [Fact]
+        public void NoParamsDeleteRemovesCookieCreatedByAdd()
+        {
+            var headers = new HeaderDictionary();
+            var cookies = new ResponseCookies(headers, null);
+            var testcookie = "TestCookie";
+
+            cookies.Append(testcookie, testcookie);
+            cookies.Delete(testcookie);
+
+            var cookieHeaderValues = headers[HeaderNames.SetCookie];
+            Assert.Single(cookieHeaderValues);
+            Assert.StartsWith(testcookie, cookieHeaderValues[0]);
+            Assert.Contains("path=/", cookieHeaderValues[0]);
+            Assert.Contains("expires=Thu, 01 Jan 1970 00:00:00 GMT", cookieHeaderValues[0]);
+        }
+
+        [Fact]
+        public void ProvidesMaxAgeWithCookieOptionsArgumentExpectMaxAgeToBeSet()
+        {
+            var headers = new HeaderDictionary();
+            var cookies = new ResponseCookies(headers, null);
+            var cookieOptions = new CookieOptions();
+            var maxAgeTime = TimeSpan.FromHours(1);
+            cookieOptions.MaxAge = TimeSpan.FromHours(1);
+            var testcookie = "TestCookie";
+
+            cookies.Append(testcookie, testcookie, cookieOptions);
+
+            var cookieHeaderValues = headers[HeaderNames.SetCookie];
+            Assert.Single(cookieHeaderValues);
+            Assert.Contains($"max-age={maxAgeTime.TotalSeconds.ToString()}", cookieHeaderValues[0]);
+        }
+
+        public static TheoryData EscapesKeyValuesBeforeSettingCookieData
+        {
+            get
+            {
+                // key, value, object pool, expected
+                return new TheoryData<string, string, string>
+                {
+                    { "key", "value", "key=value" },
+                    { "key,", "!value", "key%2C=%21value" },
+                    { "ke#y,", "val^ue", "ke%23y%2C=val%5Eue" },
+                    { "base64", "QUI+REU/Rw==", "base64=QUI%2BREU%2FRw%3D%3D" },
+                };
+            }
+        }
+
+        [Theory]
+        [MemberData(nameof(EscapesKeyValuesBeforeSettingCookieData))]
+        public void EscapesKeyValuesBeforeSettingCookie(
+            string key,
+            string value,
+            string expected)
+        {
+            var headers = new HeaderDictionary();
+            var cookies = new ResponseCookies(headers, null);
+
+            cookies.Append(key, value);
+
+            var cookieHeaderValues = headers[HeaderNames.SetCookie];
+            Assert.Single(cookieHeaderValues);
+            Assert.StartsWith(expected, cookieHeaderValues[0]);
+        }
+    }
+}
diff --git a/src/Http/HttpAbstractions.sln b/src/Http/HttpAbstractions.sln
new file mode 100644
index 0000000000000000000000000000000000000000..7a70d0015d092a9331498813c3d50ba150d4be3c
--- /dev/null
+++ b/src/Http/HttpAbstractions.sln
@@ -0,0 +1,312 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 15
+VisualStudioVersion = 15.0.26124.0
+MinimumVisualStudioVersion = 15.0.26124.0
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authentication.Abstractions", "Authentication.Abstractions", "{587C3D55-6092-4B86-99F5-E9772C9C1ADB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Authentication.Abstractions", "Authentication.Abstractions\src\Microsoft.AspNetCore.Authentication.Abstractions.csproj", "{565B7B00-96A1-49B8-9753-9E045C6527A2}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Authentication.Core", "Authentication.Core", "{B51F45A6-428F-40F4-897F-7C62C29EC39A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Authentication.Core", "Authentication.Core\src\Microsoft.AspNetCore.Authentication.Core.csproj", "{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Authentication.Core.Test", "Authentication.Core\test\Microsoft.AspNetCore.Authentication.Core.Test.csproj", "{21071749-4361-4CD0-B5ED-541C72326800}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Headers", "Headers", "{FF334B62-1AE2-477C-B91B-B28F898DFC3A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Net.Http.Headers", "Headers\src\Microsoft.Net.Http.Headers.csproj", "{D2B2E73E-A3A4-4996-906C-6647CD7D2634}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Net.Http.Headers.Tests", "Headers\test\Microsoft.Net.Http.Headers.Tests.csproj", "{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http", "Http", "{FB2DCA0F-EB9E-425B-ABBC-D543DBEC090F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http", "Http\src\Microsoft.AspNetCore.Http.csproj", "{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Tests", "Http\test\Microsoft.AspNetCore.Http.Tests.csproj", "{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http.Abstractions", "Http.Abstractions", "{28F3D5CC-1F8E-4E15-94C8-E432DFA0A702}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Abstractions", "Http.Abstractions\src\Microsoft.AspNetCore.Http.Abstractions.csproj", "{D079CD1C-A18F-4457-91BC-432577D2FD37}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Abstractions.Tests", "Http.Abstractions\test\Microsoft.AspNetCore.Http.Abstractions.Tests.csproj", "{C28045AC-FF16-468C-A1E8-EC192DA2EF19}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http.Extensions", "Http.Extensions", "{CCC61332-7D63-4DDB-B604-884670157624}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Extensions", "Http.Extensions\src\Microsoft.AspNetCore.Http.Extensions.csproj", "{C06F2A33-B887-46BB-8F51-2666EDBE5D38}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Extensions.Tests", "Http.Extensions\test\Microsoft.AspNetCore.Http.Extensions.Tests.csproj", "{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Http.Features", "Http.Features", "{0B1B3E58-DA37-46D6-B791-47739EF27790}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Features", "Http.Features\src\Microsoft.AspNetCore.Http.Features.csproj", "{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.Features.Tests", "Http.Features\test\Microsoft.AspNetCore.Http.Features.Tests.csproj", "{5A64C915-7045-4100-B2CB-3A50BD854D2D}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Owin", "Owin", "{4D5C4F16-5DC5-4244-A10F-08545126F61B}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Owin", "Owin\src\Microsoft.AspNetCore.Owin.csproj", "{21624719-422E-4621-A17A-C6F10436F1FE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Owin.Tests", "Owin\test\Microsoft.AspNetCore.Owin.Tests.csproj", "{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{391FBA36-BEEB-411A-A588-3F83901C0C1A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp", "samples\SampleApp\SampleApp.csproj", "{2378049E-ABE9-4843-AAC7-A6C9E704463D}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebUtilities", "WebUtilities", "{80A090C8-ED02-4DE3-875A-30DCCDBD84BA}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.WebUtilities", "WebUtilities\src\Microsoft.AspNetCore.WebUtilities.csproj", "{1A866315-5FD5-4F96-BFAC-1447E3CB4514}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.WebUtilities.Tests", "WebUtilities\test\Microsoft.AspNetCore.WebUtilities.Tests.csproj", "{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Debug|x64 = Debug|x64
+		Debug|x86 = Debug|x86
+		Release|Any CPU = Release|Any CPU
+		Release|x64 = Release|x64
+		Release|x86 = Release|x86
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{565B7B00-96A1-49B8-9753-9E045C6527A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{565B7B00-96A1-49B8-9753-9E045C6527A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{565B7B00-96A1-49B8-9753-9E045C6527A2}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{565B7B00-96A1-49B8-9753-9E045C6527A2}.Debug|x64.Build.0 = Debug|Any CPU
+		{565B7B00-96A1-49B8-9753-9E045C6527A2}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{565B7B00-96A1-49B8-9753-9E045C6527A2}.Debug|x86.Build.0 = Debug|Any CPU
+		{565B7B00-96A1-49B8-9753-9E045C6527A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{565B7B00-96A1-49B8-9753-9E045C6527A2}.Release|Any CPU.Build.0 = Release|Any CPU
+		{565B7B00-96A1-49B8-9753-9E045C6527A2}.Release|x64.ActiveCfg = Release|Any CPU
+		{565B7B00-96A1-49B8-9753-9E045C6527A2}.Release|x64.Build.0 = Release|Any CPU
+		{565B7B00-96A1-49B8-9753-9E045C6527A2}.Release|x86.ActiveCfg = Release|Any CPU
+		{565B7B00-96A1-49B8-9753-9E045C6527A2}.Release|x86.Build.0 = Release|Any CPU
+		{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96}.Debug|x64.Build.0 = Debug|Any CPU
+		{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96}.Debug|x86.Build.0 = Debug|Any CPU
+		{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96}.Release|x64.ActiveCfg = Release|Any CPU
+		{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96}.Release|x64.Build.0 = Release|Any CPU
+		{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96}.Release|x86.ActiveCfg = Release|Any CPU
+		{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96}.Release|x86.Build.0 = Release|Any CPU
+		{21071749-4361-4CD0-B5ED-541C72326800}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{21071749-4361-4CD0-B5ED-541C72326800}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{21071749-4361-4CD0-B5ED-541C72326800}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{21071749-4361-4CD0-B5ED-541C72326800}.Debug|x64.Build.0 = Debug|Any CPU
+		{21071749-4361-4CD0-B5ED-541C72326800}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{21071749-4361-4CD0-B5ED-541C72326800}.Debug|x86.Build.0 = Debug|Any CPU
+		{21071749-4361-4CD0-B5ED-541C72326800}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{21071749-4361-4CD0-B5ED-541C72326800}.Release|Any CPU.Build.0 = Release|Any CPU
+		{21071749-4361-4CD0-B5ED-541C72326800}.Release|x64.ActiveCfg = Release|Any CPU
+		{21071749-4361-4CD0-B5ED-541C72326800}.Release|x64.Build.0 = Release|Any CPU
+		{21071749-4361-4CD0-B5ED-541C72326800}.Release|x86.ActiveCfg = Release|Any CPU
+		{21071749-4361-4CD0-B5ED-541C72326800}.Release|x86.Build.0 = Release|Any CPU
+		{D2B2E73E-A3A4-4996-906C-6647CD7D2634}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{D2B2E73E-A3A4-4996-906C-6647CD7D2634}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D2B2E73E-A3A4-4996-906C-6647CD7D2634}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{D2B2E73E-A3A4-4996-906C-6647CD7D2634}.Debug|x64.Build.0 = Debug|Any CPU
+		{D2B2E73E-A3A4-4996-906C-6647CD7D2634}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{D2B2E73E-A3A4-4996-906C-6647CD7D2634}.Debug|x86.Build.0 = Debug|Any CPU
+		{D2B2E73E-A3A4-4996-906C-6647CD7D2634}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D2B2E73E-A3A4-4996-906C-6647CD7D2634}.Release|Any CPU.Build.0 = Release|Any CPU
+		{D2B2E73E-A3A4-4996-906C-6647CD7D2634}.Release|x64.ActiveCfg = Release|Any CPU
+		{D2B2E73E-A3A4-4996-906C-6647CD7D2634}.Release|x64.Build.0 = Release|Any CPU
+		{D2B2E73E-A3A4-4996-906C-6647CD7D2634}.Release|x86.ActiveCfg = Release|Any CPU
+		{D2B2E73E-A3A4-4996-906C-6647CD7D2634}.Release|x86.Build.0 = Release|Any CPU
+		{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF}.Debug|x64.Build.0 = Debug|Any CPU
+		{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF}.Debug|x86.Build.0 = Debug|Any CPU
+		{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF}.Release|Any CPU.Build.0 = Release|Any CPU
+		{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF}.Release|x64.ActiveCfg = Release|Any CPU
+		{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF}.Release|x64.Build.0 = Release|Any CPU
+		{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF}.Release|x86.ActiveCfg = Release|Any CPU
+		{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF}.Release|x86.Build.0 = Release|Any CPU
+		{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F}.Debug|x64.Build.0 = Debug|Any CPU
+		{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F}.Debug|x86.Build.0 = Debug|Any CPU
+		{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F}.Release|Any CPU.Build.0 = Release|Any CPU
+		{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F}.Release|x64.ActiveCfg = Release|Any CPU
+		{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F}.Release|x64.Build.0 = Release|Any CPU
+		{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F}.Release|x86.ActiveCfg = Release|Any CPU
+		{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F}.Release|x86.Build.0 = Release|Any CPU
+		{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891}.Debug|x64.Build.0 = Debug|Any CPU
+		{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891}.Debug|x86.Build.0 = Debug|Any CPU
+		{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891}.Release|Any CPU.Build.0 = Release|Any CPU
+		{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891}.Release|x64.ActiveCfg = Release|Any CPU
+		{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891}.Release|x64.Build.0 = Release|Any CPU
+		{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891}.Release|x86.ActiveCfg = Release|Any CPU
+		{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891}.Release|x86.Build.0 = Release|Any CPU
+		{D079CD1C-A18F-4457-91BC-432577D2FD37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{D079CD1C-A18F-4457-91BC-432577D2FD37}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D079CD1C-A18F-4457-91BC-432577D2FD37}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{D079CD1C-A18F-4457-91BC-432577D2FD37}.Debug|x64.Build.0 = Debug|Any CPU
+		{D079CD1C-A18F-4457-91BC-432577D2FD37}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{D079CD1C-A18F-4457-91BC-432577D2FD37}.Debug|x86.Build.0 = Debug|Any CPU
+		{D079CD1C-A18F-4457-91BC-432577D2FD37}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D079CD1C-A18F-4457-91BC-432577D2FD37}.Release|Any CPU.Build.0 = Release|Any CPU
+		{D079CD1C-A18F-4457-91BC-432577D2FD37}.Release|x64.ActiveCfg = Release|Any CPU
+		{D079CD1C-A18F-4457-91BC-432577D2FD37}.Release|x64.Build.0 = Release|Any CPU
+		{D079CD1C-A18F-4457-91BC-432577D2FD37}.Release|x86.ActiveCfg = Release|Any CPU
+		{D079CD1C-A18F-4457-91BC-432577D2FD37}.Release|x86.Build.0 = Release|Any CPU
+		{C28045AC-FF16-468C-A1E8-EC192DA2EF19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C28045AC-FF16-468C-A1E8-EC192DA2EF19}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C28045AC-FF16-468C-A1E8-EC192DA2EF19}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{C28045AC-FF16-468C-A1E8-EC192DA2EF19}.Debug|x64.Build.0 = Debug|Any CPU
+		{C28045AC-FF16-468C-A1E8-EC192DA2EF19}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{C28045AC-FF16-468C-A1E8-EC192DA2EF19}.Debug|x86.Build.0 = Debug|Any CPU
+		{C28045AC-FF16-468C-A1E8-EC192DA2EF19}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C28045AC-FF16-468C-A1E8-EC192DA2EF19}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C28045AC-FF16-468C-A1E8-EC192DA2EF19}.Release|x64.ActiveCfg = Release|Any CPU
+		{C28045AC-FF16-468C-A1E8-EC192DA2EF19}.Release|x64.Build.0 = Release|Any CPU
+		{C28045AC-FF16-468C-A1E8-EC192DA2EF19}.Release|x86.ActiveCfg = Release|Any CPU
+		{C28045AC-FF16-468C-A1E8-EC192DA2EF19}.Release|x86.Build.0 = Release|Any CPU
+		{C06F2A33-B887-46BB-8F51-2666EDBE5D38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C06F2A33-B887-46BB-8F51-2666EDBE5D38}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C06F2A33-B887-46BB-8F51-2666EDBE5D38}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{C06F2A33-B887-46BB-8F51-2666EDBE5D38}.Debug|x64.Build.0 = Debug|Any CPU
+		{C06F2A33-B887-46BB-8F51-2666EDBE5D38}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{C06F2A33-B887-46BB-8F51-2666EDBE5D38}.Debug|x86.Build.0 = Debug|Any CPU
+		{C06F2A33-B887-46BB-8F51-2666EDBE5D38}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C06F2A33-B887-46BB-8F51-2666EDBE5D38}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C06F2A33-B887-46BB-8F51-2666EDBE5D38}.Release|x64.ActiveCfg = Release|Any CPU
+		{C06F2A33-B887-46BB-8F51-2666EDBE5D38}.Release|x64.Build.0 = Release|Any CPU
+		{C06F2A33-B887-46BB-8F51-2666EDBE5D38}.Release|x86.ActiveCfg = Release|Any CPU
+		{C06F2A33-B887-46BB-8F51-2666EDBE5D38}.Release|x86.Build.0 = Release|Any CPU
+		{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07}.Debug|x64.Build.0 = Debug|Any CPU
+		{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07}.Debug|x86.Build.0 = Debug|Any CPU
+		{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07}.Release|Any CPU.Build.0 = Release|Any CPU
+		{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07}.Release|x64.ActiveCfg = Release|Any CPU
+		{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07}.Release|x64.Build.0 = Release|Any CPU
+		{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07}.Release|x86.ActiveCfg = Release|Any CPU
+		{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07}.Release|x86.Build.0 = Release|Any CPU
+		{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6}.Debug|x64.Build.0 = Debug|Any CPU
+		{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6}.Debug|x86.Build.0 = Debug|Any CPU
+		{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6}.Release|x64.ActiveCfg = Release|Any CPU
+		{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6}.Release|x64.Build.0 = Release|Any CPU
+		{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6}.Release|x86.ActiveCfg = Release|Any CPU
+		{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6}.Release|x86.Build.0 = Release|Any CPU
+		{5A64C915-7045-4100-B2CB-3A50BD854D2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{5A64C915-7045-4100-B2CB-3A50BD854D2D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{5A64C915-7045-4100-B2CB-3A50BD854D2D}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{5A64C915-7045-4100-B2CB-3A50BD854D2D}.Debug|x64.Build.0 = Debug|Any CPU
+		{5A64C915-7045-4100-B2CB-3A50BD854D2D}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{5A64C915-7045-4100-B2CB-3A50BD854D2D}.Debug|x86.Build.0 = Debug|Any CPU
+		{5A64C915-7045-4100-B2CB-3A50BD854D2D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{5A64C915-7045-4100-B2CB-3A50BD854D2D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{5A64C915-7045-4100-B2CB-3A50BD854D2D}.Release|x64.ActiveCfg = Release|Any CPU
+		{5A64C915-7045-4100-B2CB-3A50BD854D2D}.Release|x64.Build.0 = Release|Any CPU
+		{5A64C915-7045-4100-B2CB-3A50BD854D2D}.Release|x86.ActiveCfg = Release|Any CPU
+		{5A64C915-7045-4100-B2CB-3A50BD854D2D}.Release|x86.Build.0 = Release|Any CPU
+		{21624719-422E-4621-A17A-C6F10436F1FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{21624719-422E-4621-A17A-C6F10436F1FE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{21624719-422E-4621-A17A-C6F10436F1FE}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{21624719-422E-4621-A17A-C6F10436F1FE}.Debug|x64.Build.0 = Debug|Any CPU
+		{21624719-422E-4621-A17A-C6F10436F1FE}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{21624719-422E-4621-A17A-C6F10436F1FE}.Debug|x86.Build.0 = Debug|Any CPU
+		{21624719-422E-4621-A17A-C6F10436F1FE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{21624719-422E-4621-A17A-C6F10436F1FE}.Release|Any CPU.Build.0 = Release|Any CPU
+		{21624719-422E-4621-A17A-C6F10436F1FE}.Release|x64.ActiveCfg = Release|Any CPU
+		{21624719-422E-4621-A17A-C6F10436F1FE}.Release|x64.Build.0 = Release|Any CPU
+		{21624719-422E-4621-A17A-C6F10436F1FE}.Release|x86.ActiveCfg = Release|Any CPU
+		{21624719-422E-4621-A17A-C6F10436F1FE}.Release|x86.Build.0 = Release|Any CPU
+		{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4}.Debug|x64.Build.0 = Debug|Any CPU
+		{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4}.Debug|x86.Build.0 = Debug|Any CPU
+		{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4}.Release|Any CPU.Build.0 = Release|Any CPU
+		{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4}.Release|x64.ActiveCfg = Release|Any CPU
+		{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4}.Release|x64.Build.0 = Release|Any CPU
+		{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4}.Release|x86.ActiveCfg = Release|Any CPU
+		{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4}.Release|x86.Build.0 = Release|Any CPU
+		{2378049E-ABE9-4843-AAC7-A6C9E704463D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{2378049E-ABE9-4843-AAC7-A6C9E704463D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{2378049E-ABE9-4843-AAC7-A6C9E704463D}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{2378049E-ABE9-4843-AAC7-A6C9E704463D}.Debug|x64.Build.0 = Debug|Any CPU
+		{2378049E-ABE9-4843-AAC7-A6C9E704463D}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{2378049E-ABE9-4843-AAC7-A6C9E704463D}.Debug|x86.Build.0 = Debug|Any CPU
+		{2378049E-ABE9-4843-AAC7-A6C9E704463D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{2378049E-ABE9-4843-AAC7-A6C9E704463D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{2378049E-ABE9-4843-AAC7-A6C9E704463D}.Release|x64.ActiveCfg = Release|Any CPU
+		{2378049E-ABE9-4843-AAC7-A6C9E704463D}.Release|x64.Build.0 = Release|Any CPU
+		{2378049E-ABE9-4843-AAC7-A6C9E704463D}.Release|x86.ActiveCfg = Release|Any CPU
+		{2378049E-ABE9-4843-AAC7-A6C9E704463D}.Release|x86.Build.0 = Release|Any CPU
+		{1A866315-5FD5-4F96-BFAC-1447E3CB4514}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1A866315-5FD5-4F96-BFAC-1447E3CB4514}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1A866315-5FD5-4F96-BFAC-1447E3CB4514}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{1A866315-5FD5-4F96-BFAC-1447E3CB4514}.Debug|x64.Build.0 = Debug|Any CPU
+		{1A866315-5FD5-4F96-BFAC-1447E3CB4514}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{1A866315-5FD5-4F96-BFAC-1447E3CB4514}.Debug|x86.Build.0 = Debug|Any CPU
+		{1A866315-5FD5-4F96-BFAC-1447E3CB4514}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{1A866315-5FD5-4F96-BFAC-1447E3CB4514}.Release|Any CPU.Build.0 = Release|Any CPU
+		{1A866315-5FD5-4F96-BFAC-1447E3CB4514}.Release|x64.ActiveCfg = Release|Any CPU
+		{1A866315-5FD5-4F96-BFAC-1447E3CB4514}.Release|x64.Build.0 = Release|Any CPU
+		{1A866315-5FD5-4F96-BFAC-1447E3CB4514}.Release|x86.ActiveCfg = Release|Any CPU
+		{1A866315-5FD5-4F96-BFAC-1447E3CB4514}.Release|x86.Build.0 = Release|Any CPU
+		{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Debug|x64.Build.0 = Debug|Any CPU
+		{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Debug|x86.ActiveCfg = Debug|Any CPU
+		{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Debug|x86.Build.0 = Debug|Any CPU
+		{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Release|Any CPU.Build.0 = Release|Any CPU
+		{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Release|x64.ActiveCfg = Release|Any CPU
+		{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Release|x64.Build.0 = Release|Any CPU
+		{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Release|x86.ActiveCfg = Release|Any CPU
+		{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8}.Release|x86.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(NestedProjects) = preSolution
+		{565B7B00-96A1-49B8-9753-9E045C6527A2} = {587C3D55-6092-4B86-99F5-E9772C9C1ADB}
+		{A3DEE5E8-FC9D-4135-8CDB-24E5BF954F96} = {B51F45A6-428F-40F4-897F-7C62C29EC39A}
+		{21071749-4361-4CD0-B5ED-541C72326800} = {B51F45A6-428F-40F4-897F-7C62C29EC39A}
+		{D2B2E73E-A3A4-4996-906C-6647CD7D2634} = {FF334B62-1AE2-477C-B91B-B28F898DFC3A}
+		{9CE486B4-0BC6-4C71-AA7C-BD66E78E11CF} = {FF334B62-1AE2-477C-B91B-B28F898DFC3A}
+		{E35F0A95-0016-4B4D-BB85-ADB4CFAD857F} = {FB2DCA0F-EB9E-425B-ABBC-D543DBEC090F}
+		{D9155D31-0844-4ED6-AC7B-6C4C9DA6E891} = {FB2DCA0F-EB9E-425B-ABBC-D543DBEC090F}
+		{D079CD1C-A18F-4457-91BC-432577D2FD37} = {28F3D5CC-1F8E-4E15-94C8-E432DFA0A702}
+		{C28045AC-FF16-468C-A1E8-EC192DA2EF19} = {28F3D5CC-1F8E-4E15-94C8-E432DFA0A702}
+		{C06F2A33-B887-46BB-8F51-2666EDBE5D38} = {CCC61332-7D63-4DDB-B604-884670157624}
+		{BC50C116-2F25-4BC9-BDDC-7B3BA4A0BA07} = {CCC61332-7D63-4DDB-B604-884670157624}
+		{F6DEA0F5-79D0-4BC9-BFC9-CA6360B8B4E6} = {0B1B3E58-DA37-46D6-B791-47739EF27790}
+		{5A64C915-7045-4100-B2CB-3A50BD854D2D} = {0B1B3E58-DA37-46D6-B791-47739EF27790}
+		{21624719-422E-4621-A17A-C6F10436F1FE} = {4D5C4F16-5DC5-4244-A10F-08545126F61B}
+		{38EA14B3-17BB-44F4-A9EA-A8675E9BF1E4} = {4D5C4F16-5DC5-4244-A10F-08545126F61B}
+		{2378049E-ABE9-4843-AAC7-A6C9E704463D} = {391FBA36-BEEB-411A-A588-3F83901C0C1A}
+		{1A866315-5FD5-4F96-BFAC-1447E3CB4514} = {80A090C8-ED02-4DE3-875A-30DCCDBD84BA}
+		{068A1DA0-C7DF-4E3C-9933-4E79A141EFF8} = {80A090C8-ED02-4DE3-875A-30DCCDBD84BA}
+	EndGlobalSection
+EndGlobal
diff --git a/src/Http/Owin/src/DictionaryStringArrayWrapper.cs b/src/Http/Owin/src/DictionaryStringArrayWrapper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c4bb38f386363cd264dd9faad80259bb274be02d
--- /dev/null
+++ b/src/Http/Owin/src/DictionaryStringArrayWrapper.cs
@@ -0,0 +1,81 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Owin
+{
+    internal class DictionaryStringArrayWrapper : IDictionary<string, string[]>
+    {
+        public DictionaryStringArrayWrapper(IHeaderDictionary inner)
+        {
+            Inner = inner;
+        }
+
+        public readonly IHeaderDictionary Inner;
+
+        private KeyValuePair<string, StringValues> Convert(KeyValuePair<string, string[]> item) => new KeyValuePair<string, StringValues>(item.Key, item.Value);
+
+        private KeyValuePair<string, string[]> Convert(KeyValuePair<string, StringValues> item) => new KeyValuePair<string, string[]>(item.Key, item.Value);
+
+        private StringValues Convert(string[] item) => item;
+
+        private string[] Convert(StringValues item) => item;
+
+        string[] IDictionary<string, string[]>.this[string key]
+        {
+            get { return ((IDictionary<string, StringValues>)Inner)[key]; }
+            set { Inner[key] = value; }
+        }
+
+        int ICollection<KeyValuePair<string, string[]>>.Count => Inner.Count;
+
+        bool ICollection<KeyValuePair<string, string[]>>.IsReadOnly => Inner.IsReadOnly;
+
+        ICollection<string> IDictionary<string, string[]>.Keys => Inner.Keys;
+
+        ICollection<string[]> IDictionary<string, string[]>.Values => Inner.Values.Select(Convert).ToList();
+
+        void ICollection<KeyValuePair<string, string[]>>.Add(KeyValuePair<string, string[]> item) => Inner.Add(Convert(item));
+
+        void IDictionary<string, string[]>.Add(string key, string[] value) => Inner.Add(key, value);
+
+        void ICollection<KeyValuePair<string, string[]>>.Clear() => Inner.Clear();
+
+        bool ICollection<KeyValuePair<string, string[]>>.Contains(KeyValuePair<string, string[]> item) => Inner.Contains(Convert(item));
+
+        bool IDictionary<string, string[]>.ContainsKey(string key) => Inner.ContainsKey(key);
+
+        void ICollection<KeyValuePair<string, string[]>>.CopyTo(KeyValuePair<string, string[]>[] array, int arrayIndex)
+        {
+            foreach(var kv in Inner)
+            {
+                array[arrayIndex++] = Convert(kv);
+            }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator() => Inner.Select(Convert).GetEnumerator();
+
+        IEnumerator<KeyValuePair<string, string[]>> IEnumerable<KeyValuePair<string, string[]>>.GetEnumerator() => Inner.Select(Convert).GetEnumerator();
+
+        bool ICollection<KeyValuePair<string, string[]>>.Remove(KeyValuePair<string, string[]> item) => Inner.Remove(Convert(item));
+
+        bool IDictionary<string, string[]>.Remove(string key) => Inner.Remove(key);
+
+        bool IDictionary<string, string[]>.TryGetValue(string key, out string[] value)
+        {
+            StringValues temp;
+            if (Inner.TryGetValue(key, out temp))
+            {
+                value = temp;
+                return true;
+            }
+            value = default(StringValues);
+            return false;
+        }
+    }
+}
diff --git a/src/Http/Owin/src/DictionaryStringValuesWrapper.cs b/src/Http/Owin/src/DictionaryStringValuesWrapper.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b31c7e9790c1e79d567bbe2c8cb8d0909771240a
--- /dev/null
+++ b/src/Http/Owin/src/DictionaryStringValuesWrapper.cs
@@ -0,0 +1,126 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Primitives;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.Owin
+{
+    internal class DictionaryStringValuesWrapper : IHeaderDictionary
+    {
+        public DictionaryStringValuesWrapper(IDictionary<string, string[]> inner)
+        {
+            Inner = inner;
+        }
+
+        public readonly IDictionary<string, string[]> Inner;
+
+        private KeyValuePair<string, StringValues> Convert(KeyValuePair<string, string[]> item) => new KeyValuePair<string, StringValues>(item.Key, item.Value);
+
+        private KeyValuePair<string, string[]> Convert(KeyValuePair<string, StringValues> item) => new KeyValuePair<string, string[]>(item.Key, item.Value);
+
+        private StringValues Convert(string[] item) => item;
+
+        private string[] Convert(StringValues item) => item;
+
+        StringValues IHeaderDictionary.this[string key]
+        {
+            get
+            {
+                string[] values;
+                return Inner.TryGetValue(key, out values) ? values : null;
+            }
+            set { Inner[key] = value; }
+        }
+
+        StringValues IDictionary<string, StringValues>.this[string key]
+        {
+            get { return Inner[key]; }
+            set { Inner[key] = value; }
+        }
+
+        public long? ContentLength
+        {
+            get
+            {
+                long value;
+
+                string[] rawValue;
+                if (!Inner.TryGetValue(HeaderNames.ContentLength, out rawValue))
+                {
+                    return null;
+                }
+
+                if (rawValue.Length == 1 &&
+                    !string.IsNullOrEmpty(rawValue[0]) &&
+                    HeaderUtilities.TryParseNonNegativeInt64(new StringSegment(rawValue[0]).Trim(), out value))
+                {
+                    return value;
+                }
+
+                return null;
+            }
+            set
+            {
+                if (value.HasValue)
+                {
+                    Inner[HeaderNames.ContentLength] = (StringValues)HeaderUtilities.FormatNonNegativeInt64(value.Value);
+                }
+                else
+                {
+                    Inner.Remove(HeaderNames.ContentLength);
+                }
+            }
+        }
+
+        int ICollection<KeyValuePair<string, StringValues>>.Count => Inner.Count;
+
+        bool ICollection<KeyValuePair<string, StringValues>>.IsReadOnly => Inner.IsReadOnly;
+
+        ICollection<string> IDictionary<string, StringValues>.Keys => Inner.Keys;
+
+        ICollection<StringValues> IDictionary<string, StringValues>.Values => Inner.Values.Select(Convert).ToList();
+
+        void ICollection<KeyValuePair<string, StringValues>>.Add(KeyValuePair<string, StringValues> item) => Inner.Add(Convert(item));
+
+        void IDictionary<string, StringValues>.Add(string key, StringValues value) => Inner.Add(key, value);
+
+        void ICollection<KeyValuePair<string, StringValues>>.Clear() => Inner.Clear();
+
+        bool ICollection<KeyValuePair<string, StringValues>>.Contains(KeyValuePair<string, StringValues> item) => Inner.Contains(Convert(item));
+
+        bool IDictionary<string, StringValues>.ContainsKey(string key) => Inner.ContainsKey(key);
+
+        void ICollection<KeyValuePair<string, StringValues>>.CopyTo(KeyValuePair<string, StringValues>[] array, int arrayIndex)
+        {
+            foreach (var kv in Inner)
+            {
+                array[arrayIndex++] = Convert(kv);
+            }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator() => Inner.Select(Convert).GetEnumerator();
+
+        IEnumerator<KeyValuePair<string, StringValues>> IEnumerable<KeyValuePair<string, StringValues>>.GetEnumerator() => Inner.Select(Convert).GetEnumerator();
+
+        bool ICollection<KeyValuePair<string, StringValues>>.Remove(KeyValuePair<string, StringValues> item) => Inner.Remove(Convert(item));
+
+        bool IDictionary<string, StringValues>.Remove(string key) => Inner.Remove(key);
+
+        bool IDictionary<string, StringValues>.TryGetValue(string key, out StringValues value)
+        {
+            string[] temp;
+            if (Inner.TryGetValue(key, out temp))
+            {
+                value = temp;
+                return true;
+            }
+            value = default(StringValues);
+            return false;
+        }
+    }
+}
diff --git a/src/Http/Owin/src/IOwinEnvironmentFeature.cs b/src/Http/Owin/src/IOwinEnvironmentFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8a476b9f38f2b49d903ac8aedae5f2382355861c
--- /dev/null
+++ b/src/Http/Owin/src/IOwinEnvironmentFeature.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.
+
+using System.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Owin
+{
+    public interface IOwinEnvironmentFeature
+    {
+        IDictionary<string, object> Environment { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Owin/src/Microsoft.AspNetCore.Owin.csproj b/src/Http/Owin/src/Microsoft.AspNetCore.Owin.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..cf9574d7f82a85eac8104257543c203aa69ac04b
--- /dev/null
+++ b/src/Http/Owin/src/Microsoft.AspNetCore.Owin.csproj
@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>ASP.NET Core component for running OWIN middleware in an ASP.NET Core application, and to run ASP.NET Core middleware in an OWIN application.</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <NoWarn>$(NoWarn);CS1591</NoWarn>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore;owin</PackageTags>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Http" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/Owin/src/OwinConstants.cs b/src/Http/Owin/src/OwinConstants.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4234b65aa68516cfd49bd6172c14902e7f5ce835
--- /dev/null
+++ b/src/Http/Owin/src/OwinConstants.cs
@@ -0,0 +1,177 @@
+// 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.Owin
+{
+    internal static class OwinConstants
+    {
+        #region OWIN v1.0.0 - 3.2.1. Request Data
+
+        // http://owin.org/spec/spec/owin-1.0.0.html
+
+        public const string RequestScheme = "owin.RequestScheme";
+        public const string RequestMethod = "owin.RequestMethod";
+        public const string RequestPathBase = "owin.RequestPathBase";
+        public const string RequestPath = "owin.RequestPath";
+        public const string RequestQueryString = "owin.RequestQueryString";
+        public const string RequestProtocol = "owin.RequestProtocol";
+        public const string RequestHeaders = "owin.RequestHeaders";
+        public const string RequestBody = "owin.RequestBody";
+
+        #endregion
+
+        #region OWIN v1.0.1 - 3.2.1 Request Data
+
+        // OWIN 1.0.1 http://owin.org/html/owin.html
+
+        public const string RequestId = "owin.RequestId";
+        public const string RequestUser = "owin.RequestUser";
+
+        #endregion
+
+        #region OWIN v1.0.0 - 3.2.2. Response Data
+
+        // http://owin.org/spec/spec/owin-1.0.0.html
+
+        public const string ResponseStatusCode = "owin.ResponseStatusCode";
+        public const string ResponseReasonPhrase = "owin.ResponseReasonPhrase";
+        public const string ResponseProtocol = "owin.ResponseProtocol";
+        public const string ResponseHeaders = "owin.ResponseHeaders";
+        public const string ResponseBody = "owin.ResponseBody";
+
+        #endregion
+
+        #region OWIN v1.0.0 - 3.2.3. Other Data
+
+        // http://owin.org/spec/spec/owin-1.0.0.html
+
+        public const string CallCancelled = "owin.CallCancelled";
+
+        public const string OwinVersion = "owin.Version";
+
+        #endregion
+
+        #region OWIN Keys for IAppBuilder.Properties
+
+        internal static class Builder
+        {
+            public const string AddSignatureConversion = "builder.AddSignatureConversion";
+            public const string DefaultApp = "builder.DefaultApp";
+        }
+
+        #endregion
+
+        #region OWIN Key Guidelines and Common Keys - 6. Common keys
+
+        // http://owin.org/spec/spec/CommonKeys.html
+
+        internal static class CommonKeys
+        {
+            public const string ClientCertificate = "ssl.ClientCertificate";
+            public const string LoadClientCertAsync = "ssl.LoadClientCertAsync";
+            public const string RemoteIpAddress = "server.RemoteIpAddress";
+            public const string RemotePort = "server.RemotePort";
+            public const string LocalIpAddress = "server.LocalIpAddress";
+            public const string LocalPort = "server.LocalPort";
+            public const string ConnectionId = "server.ConnectionId";
+            public const string TraceOutput = "host.TraceOutput";
+            public const string Addresses = "host.Addresses";
+            public const string AppName = "host.AppName";
+            public const string Capabilities = "server.Capabilities";
+            public const string OnSendingHeaders = "server.OnSendingHeaders";
+            public const string OnAppDisposing = "host.OnAppDisposing";
+            public const string Scheme = "scheme";
+            public const string Host = "host";
+            public const string Port = "port";
+            public const string Path = "path";
+        }
+
+        #endregion
+
+        #region SendFiles v0.3.0
+
+        // http://owin.org/spec/extensions/owin-SendFile-Extension-v0.3.0.htm
+
+        internal static class SendFiles
+        {
+            // 3.1. Startup
+
+            public const string Version = "sendfile.Version";
+            public const string Support = "sendfile.Support";
+            public const string Concurrency = "sendfile.Concurrency";
+
+            // 3.2. Per Request
+
+            public const string SendAsync = "sendfile.SendAsync";
+        }
+
+        #endregion
+
+        #region Opaque v0.3.0
+
+        // http://owin.org/spec/extensions/owin-OpaqueStream-Extension-v0.3.0.htm
+
+        internal static class OpaqueConstants
+        {
+            // 3.1. Startup
+
+            public const string Version = "opaque.Version";
+
+            // 3.2. Per Request
+
+            public const string Upgrade = "opaque.Upgrade";
+
+            // 5. Consumption
+
+            public const string Stream = "opaque.Stream";
+            // public const string Version = "opaque.Version"; // redundant, declared above
+            public const string CallCancelled = "opaque.CallCancelled";
+        }
+
+        #endregion
+
+        #region WebSocket v0.4.0
+
+        // http://owin.org/spec/extensions/owin-OpaqueStream-Extension-v0.3.0.htm
+
+        internal static class WebSocket
+        {
+            // 3.1. Startup
+
+            public const string Version = "websocket.Version";
+            public const string VersionValue = "1.0";
+
+            // 3.2. Per Request
+
+            public const string Accept = "websocket.Accept";
+            public const string AcceptAlt = "websocket.AcceptAlt"; // Non-spec
+
+            // 4. Accept
+
+            public const string SubProtocol = "websocket.SubProtocol";
+
+            // 5. Consumption
+
+            public const string SendAsync = "websocket.SendAsync";
+            public const string ReceiveAsync = "websocket.ReceiveAsync";
+            public const string CloseAsync = "websocket.CloseAsync";
+            // public const string Version = "websocket.Version"; // redundant, declared above
+            public const string CallCancelled = "websocket.CallCancelled";
+            public const string ClientCloseStatus = "websocket.ClientCloseStatus";
+            public const string ClientCloseDescription = "websocket.ClientCloseDescription";
+        }
+
+        #endregion
+
+        #region Security v0.1.0
+
+        internal static class Security
+        {
+            // 3.2. Per Request
+
+            public const string User = "server.User";
+        }
+
+        #endregion
+    }
+}
diff --git a/src/Http/Owin/src/OwinEnvironment.cs b/src/Http/Owin/src/OwinEnvironment.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6c7f3ad66f0cad135f33fc7a5af3eb8fbcdb4bba
--- /dev/null
+++ b/src/Http/Owin/src/OwinEnvironment.cs
@@ -0,0 +1,397 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Net.WebSockets;
+using System.Security.Claims;
+using System.Security.Cryptography.X509Certificates;
+using System.Security.Principal;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Http.Features.Authentication;
+
+namespace Microsoft.AspNetCore.Owin
+{
+    using SendFileFunc = Func<string, long, long?, CancellationToken, Task>;
+    using WebSocketAcceptAlt =
+        Func
+        <
+            WebSocketAcceptContext, // WebSocket Accept parameters
+            Task<WebSocket>
+        >;
+
+    public class OwinEnvironment : IDictionary<string, object>
+    {
+        private HttpContext _context;
+        private IDictionary<string, FeatureMap> _entries;
+
+        public OwinEnvironment(HttpContext context)
+        {
+            if (context.Features.Get<IHttpRequestFeature>() == null)
+            {
+                throw new ArgumentException("Missing required feature: " + nameof(IHttpRequestFeature) + ".", nameof(context));
+            }
+            if (context.Features.Get<IHttpResponseFeature>() == null)
+            {
+                throw new ArgumentException("Missing required feature: " + nameof(IHttpResponseFeature) + ".", nameof(context));
+            }
+
+            _context = context;
+            _entries = new Dictionary<string, FeatureMap>()
+            {
+                { OwinConstants.RequestProtocol, new FeatureMap<IHttpRequestFeature>(feature => feature.Protocol, () => string.Empty, (feature, value) => feature.Protocol = Convert.ToString(value)) },
+                { OwinConstants.RequestScheme, new FeatureMap<IHttpRequestFeature>(feature => feature.Scheme, () => string.Empty, (feature, value) => feature.Scheme = Convert.ToString(value)) },
+                { OwinConstants.RequestMethod, new FeatureMap<IHttpRequestFeature>(feature => feature.Method, () => string.Empty, (feature, value) => feature.Method = Convert.ToString(value)) },
+                { OwinConstants.RequestPathBase, new FeatureMap<IHttpRequestFeature>(feature => feature.PathBase, () => string.Empty, (feature, value) => feature.PathBase = Convert.ToString(value)) },
+                { OwinConstants.RequestPath, new FeatureMap<IHttpRequestFeature>(feature => feature.Path, () => string.Empty, (feature, value) => feature.Path = Convert.ToString(value)) },
+                { OwinConstants.RequestQueryString, new FeatureMap<IHttpRequestFeature>(feature => Utilities.RemoveQuestionMark(feature.QueryString), () => string.Empty,
+                    (feature, value) => feature.QueryString = Utilities.AddQuestionMark(Convert.ToString(value))) },
+                { OwinConstants.RequestHeaders, new FeatureMap<IHttpRequestFeature>(feature => Utilities.MakeDictionaryStringArray(feature.Headers), (feature, value) => feature.Headers = Utilities.MakeHeaderDictionary((IDictionary<string, string[]>)value)) },
+                { OwinConstants.RequestBody, new FeatureMap<IHttpRequestFeature>(feature => feature.Body, () => Stream.Null, (feature, value) => feature.Body = (Stream)value) },
+                { OwinConstants.RequestUser, new FeatureMap<IHttpAuthenticationFeature>(feature => feature.User, () => null, (feature, value) => feature.User = (ClaimsPrincipal)value) },
+
+                { OwinConstants.ResponseStatusCode, new FeatureMap<IHttpResponseFeature>(feature => feature.StatusCode, () => 200, (feature, value) => feature.StatusCode = Convert.ToInt32(value)) },
+                { OwinConstants.ResponseReasonPhrase, new FeatureMap<IHttpResponseFeature>(feature => feature.ReasonPhrase, (feature, value) => feature.ReasonPhrase = Convert.ToString(value)) },
+                { OwinConstants.ResponseHeaders, new FeatureMap<IHttpResponseFeature>(feature => Utilities.MakeDictionaryStringArray(feature.Headers), (feature, value) => feature.Headers = Utilities.MakeHeaderDictionary((IDictionary<string, string[]>)value)) },
+                { OwinConstants.ResponseBody, new FeatureMap<IHttpResponseFeature>(feature => feature.Body, () => Stream.Null, (feature, value) => feature.Body = (Stream)value) },
+                { OwinConstants.CommonKeys.OnSendingHeaders, new FeatureMap<IHttpResponseFeature>(
+                    feature => new Action<Action<object>, object>((cb, state) => {
+                        feature.OnStarting(s =>
+                        {
+                            cb(s);
+                            return Task.CompletedTask;
+                        }, state);
+                    }))
+                },
+
+                { OwinConstants.CommonKeys.ConnectionId, new FeatureMap<IHttpConnectionFeature>(feature => feature.ConnectionId,
+                    (feature, value) => feature.ConnectionId = Convert.ToString(value, CultureInfo.InvariantCulture)) },
+
+                { OwinConstants.CommonKeys.LocalPort, new FeatureMap<IHttpConnectionFeature>(feature => feature.LocalPort.ToString(CultureInfo.InvariantCulture),
+                    (feature, value) => feature.LocalPort = Convert.ToInt32(value, CultureInfo.InvariantCulture)) },
+                { OwinConstants.CommonKeys.RemotePort, new FeatureMap<IHttpConnectionFeature>(feature => feature.RemotePort.ToString(CultureInfo.InvariantCulture),
+                    (feature, value) => feature.RemotePort = Convert.ToInt32(value, CultureInfo.InvariantCulture)) },
+
+                { OwinConstants.CommonKeys.LocalIpAddress, new FeatureMap<IHttpConnectionFeature>(feature => feature.LocalIpAddress.ToString(),
+                    (feature, value) => feature.LocalIpAddress = IPAddress.Parse(Convert.ToString(value))) },
+                { OwinConstants.CommonKeys.RemoteIpAddress, new FeatureMap<IHttpConnectionFeature>(feature => feature.RemoteIpAddress.ToString(),
+                    (feature, value) => feature.RemoteIpAddress = IPAddress.Parse(Convert.ToString(value))) },
+
+                { OwinConstants.SendFiles.SendAsync, new FeatureMap<IHttpSendFileFeature>(feature => new SendFileFunc(feature.SendFileAsync)) },
+
+                { OwinConstants.Security.User, new FeatureMap<IHttpAuthenticationFeature>(feature => feature.User,
+                    ()=> null, (feature, value) => feature.User = Utilities.MakeClaimsPrincipal((IPrincipal)value),
+                    () => new HttpAuthenticationFeature())
+                },
+
+                { OwinConstants.RequestId, new FeatureMap<IHttpRequestIdentifierFeature>(feature => feature.TraceIdentifier,
+                    ()=> null, (feature, value) => feature.TraceIdentifier = (string)value,
+                    () => new HttpRequestIdentifierFeature())
+                }
+            };
+
+            // owin.CallCancelled is required but the feature may not be present.
+            if (context.Features.Get<IHttpRequestLifetimeFeature>() != null)
+            {
+                _entries[OwinConstants.CallCancelled] = new FeatureMap<IHttpRequestLifetimeFeature>(feature => feature.RequestAborted);
+            }
+            else if (!_context.Items.ContainsKey(OwinConstants.CallCancelled))
+            {
+                _context.Items[OwinConstants.CallCancelled] = CancellationToken.None;
+            }
+
+            // owin.Version is required.
+            if (!context.Items.ContainsKey(OwinConstants.OwinVersion))
+            {
+                _context.Items[OwinConstants.OwinVersion] = "1.0";
+            }
+
+            if (context.Request.IsHttps)
+            {
+                _entries.Add(OwinConstants.CommonKeys.ClientCertificate, new FeatureMap<ITlsConnectionFeature>(feature => feature.ClientCertificate,
+                    (feature, value) => feature.ClientCertificate = (X509Certificate2)value));
+                _entries.Add(OwinConstants.CommonKeys.LoadClientCertAsync, new FeatureMap<ITlsConnectionFeature>(
+                    feature => new Func<Task>(() => feature.GetClientCertificateAsync(CancellationToken.None))));
+            }
+
+            if (context.WebSockets.IsWebSocketRequest)
+            {
+                _entries.Add(OwinConstants.WebSocket.AcceptAlt, new FeatureMap<IHttpWebSocketFeature>(feature => new WebSocketAcceptAlt(feature.AcceptAsync)));
+            }
+
+            _context.Items[typeof(HttpContext).FullName] = _context; // Store for lookup when we transition back out of OWIN
+        }
+
+        // Public in case there's a new/custom feature interface that needs to be added.
+        public IDictionary<string, FeatureMap> FeatureMaps
+        {
+            get { return _entries; }
+        }
+
+        void IDictionary<string, object>.Add(string key, object value)
+        {
+            if (_entries.ContainsKey(key))
+            {
+                throw new InvalidOperationException("Key already present");
+            }
+            _context.Items.Add(key, value);
+        }
+
+        bool IDictionary<string, object>.ContainsKey(string key)
+        {
+            object value;
+            return ((IDictionary<string, object>)this).TryGetValue(key, out value);
+        }
+
+        ICollection<string> IDictionary<string, object>.Keys
+        {
+            get
+            {
+                object value;
+                return _entries.Where(pair => pair.Value.TryGet(_context, out value))
+                    .Select(pair => pair.Key).Concat(_context.Items.Keys.Select(key => Convert.ToString(key))).ToList();
+            }
+        }
+
+        bool IDictionary<string, object>.Remove(string key)
+        {
+            if (_entries.Remove(key))
+            {
+                return true;
+            }
+            return _context.Items.Remove(key);
+        }
+
+        bool IDictionary<string, object>.TryGetValue(string key, out object value)
+        {
+            FeatureMap entry;
+            if (_entries.TryGetValue(key, out entry) && entry.TryGet(_context, out value))
+            {
+                return true;
+            }
+            return _context.Items.TryGetValue(key, out value);
+        }
+
+        ICollection<object> IDictionary<string, object>.Values
+        {
+            get { throw new NotImplementedException(); }
+        }
+
+        object IDictionary<string, object>.this[string key]
+        {
+            get
+            {
+                FeatureMap entry;
+                object value;
+                if (_entries.TryGetValue(key, out entry) && entry.TryGet(_context, out value))
+                {
+                    return value;
+                }
+                if (_context.Items.TryGetValue(key, out value))
+                {
+                    return value;
+                }
+                throw new KeyNotFoundException(key);
+            }
+            set
+            {
+                FeatureMap entry;
+                if (_entries.TryGetValue(key, out entry))
+                {
+                    if (entry.CanSet)
+                    {
+                        entry.Set(_context, value);
+                    }
+                    else
+                    {
+                        _entries.Remove(key);
+                        if (value != null)
+                        {
+                            _context.Items[key] = value;
+                        }
+                    }
+                }
+                else
+                {
+                    if (value == null)
+                    {
+                        _context.Items.Remove(key);
+                    }
+                    else
+                    {
+                        _context.Items[key] = value;
+                    }
+                }
+            }
+        }
+
+        void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
+        {
+            throw new NotImplementedException();
+        }
+
+        void ICollection<KeyValuePair<string, object>>.Clear()
+        {
+            _entries.Clear();
+            _context.Items.Clear();
+        }
+
+        bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
+        {
+            throw new NotImplementedException();
+        }
+
+        void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
+        {
+            throw new NotImplementedException();
+        }
+
+        int ICollection<KeyValuePair<string, object>>.Count
+        {
+            get { return _entries.Count + _context.Items.Count; }
+        }
+
+        bool ICollection<KeyValuePair<string, object>>.IsReadOnly
+        {
+            get { return false; }
+        }
+
+        bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
+        {
+            throw new NotImplementedException();
+        }
+
+        public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
+        {
+            foreach (var entryPair in _entries)
+            {
+                object value;
+                if (entryPair.Value.TryGet(_context, out value))
+                {
+                    yield return new KeyValuePair<string, object>(entryPair.Key, value);
+                }
+            }
+            foreach (var entryPair in _context.Items)
+            {
+                yield return new KeyValuePair<string, object>(Convert.ToString(entryPair.Key), entryPair.Value);
+            }
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        public class FeatureMap
+        {
+            public FeatureMap(Type featureInterface, Func<object, object> getter)
+                : this(featureInterface, getter, defaultFactory: null)
+            {
+            }
+            public FeatureMap(Type featureInterface, Func<object, object> getter, Func<object> defaultFactory)
+                : this(featureInterface, getter, defaultFactory, setter: null)
+            {
+            }
+
+            public FeatureMap(Type featureInterface, Func<object, object> getter, Action<object, object> setter)
+                : this(featureInterface, getter, defaultFactory: null, setter: setter)
+            {
+            }
+
+            public FeatureMap(Type featureInterface, Func<object, object> getter, Func<object> defaultFactory, Action<object, object> setter)
+                : this(featureInterface, getter, defaultFactory, setter, featureFactory: null)
+            {
+            }
+
+            public FeatureMap(Type featureInterface, Func<object, object> getter, Func<object> defaultFactory, Action<object, object> setter, Func<object> featureFactory)
+            {
+                FeatureInterface = featureInterface;
+                Getter = getter;
+                Setter = setter;
+                DefaultFactory = defaultFactory;
+                FeatureFactory = featureFactory;
+            }
+
+            private Type FeatureInterface { get; set; }
+            private Func<object, object> Getter { get; set; }
+            private Action<object, object> Setter { get; set; }
+            private Func<object> DefaultFactory { get; set; }
+            private Func<object> FeatureFactory { get; set; }
+
+            public bool CanSet
+            {
+                get { return Setter != null; }
+            }
+
+            internal bool TryGet(HttpContext context, out object value)
+            {
+                object featureInstance = context.Features[FeatureInterface];
+                if (featureInstance == null)
+                {
+                    value = null;
+                    return false;
+                }
+                value = Getter(featureInstance);
+                if (value == null && DefaultFactory != null)
+                {
+                    value = DefaultFactory();
+                }
+                return true;
+            }
+
+            internal void Set(HttpContext context, object value)
+            {
+                var feature = context.Features[FeatureInterface];
+                if (feature == null)
+                {
+                    if (FeatureFactory == null)
+                    {
+                        throw new InvalidOperationException("Missing feature: " + FeatureInterface.FullName); // TODO: LOC
+                    }
+                    else
+                    {
+                        feature = FeatureFactory();
+                        context.Features[FeatureInterface] = feature;
+                    }
+                }
+                Setter(feature, value);
+            }
+        }
+
+        public class FeatureMap<TFeature> : FeatureMap
+        {
+            public FeatureMap(Func<TFeature, object> getter)
+                : base(typeof(TFeature), feature => getter((TFeature)feature))
+            {
+            }
+
+            public FeatureMap(Func<TFeature, object> getter, Func<object> defaultFactory)
+                : base(typeof(TFeature), feature => getter((TFeature)feature), defaultFactory)
+            {
+            }
+
+            public FeatureMap(Func<TFeature, object> getter, Action<TFeature, object> setter)
+                : base(typeof(TFeature), feature => getter((TFeature)feature), (feature, value) => setter((TFeature)feature, value))
+            {
+            }
+
+            public FeatureMap(Func<TFeature, object> getter, Func<object> defaultFactory, Action<TFeature, object> setter)
+                : base(typeof(TFeature), feature => getter((TFeature)feature), defaultFactory, (feature, value) => setter((TFeature)feature, value))
+            {
+            }
+
+            public FeatureMap(Func<TFeature, object> getter, Func<object> defaultFactory, Action<TFeature, object> setter, Func<TFeature> featureFactory)
+                : base(typeof(TFeature), feature => getter((TFeature)feature), defaultFactory, (feature, value) => setter((TFeature)feature, value), () => featureFactory())
+            {
+            }
+        }
+    }
+}
diff --git a/src/Http/Owin/src/OwinEnvironmentFeature.cs b/src/Http/Owin/src/OwinEnvironmentFeature.cs
new file mode 100644
index 0000000000000000000000000000000000000000..14eb312608135c671a5d3addb7c3c614e439d14a
--- /dev/null
+++ b/src/Http/Owin/src/OwinEnvironmentFeature.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.
+
+using System.Collections.Generic;
+
+namespace Microsoft.AspNetCore.Owin
+{
+    public class OwinEnvironmentFeature : IOwinEnvironmentFeature
+    {
+        public IDictionary<string, object> Environment { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Owin/src/OwinExtensions.cs b/src/Http/Owin/src/OwinExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0344c1a55253dc20b581235da04900dde038ffe8
--- /dev/null
+++ b/src/Http/Owin/src/OwinExtensions.cs
@@ -0,0 +1,175 @@
+// 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.Builder.Internal;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Owin;
+
+namespace Microsoft.AspNetCore.Builder
+{
+    using AddMiddleware = Action<Func<
+          Func<IDictionary<string, object>, Task>,
+          Func<IDictionary<string, object>, Task>
+        >>;
+    using AppFunc = Func<IDictionary<string, object>, Task>;
+    using CreateMiddleware = Func<
+          Func<IDictionary<string, object>, Task>,
+          Func<IDictionary<string, object>, Task>
+        >;
+
+    public static class OwinExtensions
+    {
+        public static AddMiddleware UseOwin(this IApplicationBuilder builder)
+        {
+            if (builder == null)
+            {
+                throw new ArgumentNullException(nameof(builder));
+            }
+
+            AddMiddleware add = middleware =>
+            {
+                Func<RequestDelegate, RequestDelegate> middleware1 = next1 =>
+                {
+                    AppFunc exitMiddlware = env =>
+                    {
+                        return next1((HttpContext)env[typeof(HttpContext).FullName]);
+                    };
+                    var app = middleware(exitMiddlware);
+                    return httpContext =>
+                    {
+                        // Use the existing OWIN env if there is one.
+                        IDictionary<string, object> env;
+                        var owinEnvFeature = httpContext.Features.Get<IOwinEnvironmentFeature>();
+                        if (owinEnvFeature != null)
+                        {
+                            env = owinEnvFeature.Environment;
+                            env[typeof(HttpContext).FullName] = httpContext;
+                        }
+                        else
+                        {
+                            env = new OwinEnvironment(httpContext);
+                        }
+                        return app.Invoke(env);
+                    };
+                };
+                builder.Use(middleware1);
+            };
+            // Adapt WebSockets by default.
+            add(WebSocketAcceptAdapter.AdaptWebSockets);
+            return add;
+        }
+
+        public static IApplicationBuilder UseOwin(this IApplicationBuilder builder, Action<AddMiddleware> pipeline)
+        {
+            if (builder == null)
+            {
+                throw new ArgumentNullException(nameof(builder));
+            }
+            if (pipeline == null)
+            {
+                throw new ArgumentNullException(nameof(pipeline));
+            }
+
+            pipeline(builder.UseOwin());
+            return builder;
+        }
+
+        public static IApplicationBuilder UseBuilder(this AddMiddleware app)
+        {
+            return app.UseBuilder(serviceProvider: null);
+        }
+
+        public static IApplicationBuilder UseBuilder(this AddMiddleware app, IServiceProvider serviceProvider)
+        {
+            if (app == null)
+            {
+                throw new ArgumentNullException(nameof(app));
+            }
+
+            // Do not set ApplicationBuilder.ApplicationServices to null. May fail later due to missing services but
+            // at least that results in a more useful Exception than a NRE.
+            if (serviceProvider == null)
+            {
+                serviceProvider = new EmptyProvider();
+            }
+
+            // Adapt WebSockets by default.
+            app(OwinWebSocketAcceptAdapter.AdaptWebSockets);
+            var builder = new ApplicationBuilder(serviceProvider: serviceProvider);
+
+            var middleware = CreateMiddlewareFactory(exit =>
+            {
+                builder.Use(ignored => exit);
+                return builder.Build();
+            }, builder.ApplicationServices);
+
+            app(middleware);
+            return builder;
+        }
+
+        private static CreateMiddleware CreateMiddlewareFactory(Func<RequestDelegate, RequestDelegate> middleware, IServiceProvider services)
+        {
+            return next =>
+            {
+                var app = middleware(httpContext =>
+                {
+                    return next(httpContext.Features.Get<IOwinEnvironmentFeature>().Environment);
+                });
+
+                return env =>
+                {
+                    // Use the existing HttpContext if there is one.
+                    HttpContext context;
+                    object obj;
+                    if (env.TryGetValue(typeof(HttpContext).FullName, out obj))
+                    {
+                        context = (HttpContext)obj;
+                        context.Features.Set<IOwinEnvironmentFeature>(new OwinEnvironmentFeature() { Environment = env });
+                    }
+                    else
+                    {
+                        context = new DefaultHttpContext(
+                                    new FeatureCollection(
+                                        new OwinFeatureCollection(env)));
+                        context.RequestServices = services;
+                    }
+
+                    return app.Invoke(context);
+                };
+            };
+        }
+
+        public static AddMiddleware UseBuilder(this AddMiddleware app, Action<IApplicationBuilder> pipeline)
+        {
+            return app.UseBuilder(pipeline, serviceProvider: null);
+        }
+
+        public static AddMiddleware UseBuilder(this AddMiddleware app, Action<IApplicationBuilder> pipeline, IServiceProvider serviceProvider)
+        {
+            if (app == null)
+            {
+                throw new ArgumentNullException(nameof(app));
+            }
+            if (pipeline == null)
+            {
+                throw new ArgumentNullException(nameof(pipeline));
+            }
+
+            var builder = app.UseBuilder(serviceProvider);
+            pipeline(builder);
+            return app;
+        }
+
+        private class EmptyProvider : IServiceProvider
+        {
+            public object GetService(Type serviceType)
+            {
+                return null;
+            }
+        }
+    }
+}
diff --git a/src/Http/Owin/src/OwinFeatureCollection.cs b/src/Http/Owin/src/OwinFeatureCollection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..4838b99f5cd0bc73e546e10b60a075a8db8cf099
--- /dev/null
+++ b/src/Http/Owin/src/OwinFeatureCollection.cs
@@ -0,0 +1,412 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Net;
+using System.Net.WebSockets;
+using System.Reflection;
+using System.Security.Claims;
+using System.Security.Cryptography.X509Certificates;
+using System.Security.Principal;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Http.Features.Authentication;
+
+namespace Microsoft.AspNetCore.Owin
+{
+    using SendFileFunc = Func<string, long, long?, CancellationToken, Task>;
+
+    public class OwinFeatureCollection :
+        IFeatureCollection,
+        IHttpRequestFeature,
+        IHttpResponseFeature,
+        IHttpConnectionFeature,
+        IHttpSendFileFeature,
+        ITlsConnectionFeature,
+        IHttpRequestIdentifierFeature,
+        IHttpRequestLifetimeFeature,
+        IHttpAuthenticationFeature,
+        IHttpWebSocketFeature,
+        IOwinEnvironmentFeature
+    {
+        public IDictionary<string, object> Environment { get; set; }
+        private bool _headersSent;
+
+        public OwinFeatureCollection(IDictionary<string, object> environment)
+        {
+            Environment = environment;
+            SupportsWebSockets = true;
+
+            var register = Prop<Action<Action<object>, object>>(OwinConstants.CommonKeys.OnSendingHeaders);
+            register?.Invoke(state =>
+            {
+                var collection = (OwinFeatureCollection)state;
+                collection._headersSent = true;
+            }, this);
+        }
+
+        T Prop<T>(string key)
+        {
+            object value;
+            if (Environment.TryGetValue(key, out value) && value is T)
+            {
+                return (T)value;
+            }
+            return default(T);
+        }
+
+        void Prop(string key, object value)
+        {
+            Environment[key] = value;
+        }
+
+        string IHttpRequestFeature.Protocol
+        {
+            get { return Prop<string>(OwinConstants.RequestProtocol); }
+            set { Prop(OwinConstants.RequestProtocol, value); }
+        }
+
+        string IHttpRequestFeature.Scheme
+        {
+            get { return Prop<string>(OwinConstants.RequestScheme); }
+            set { Prop(OwinConstants.RequestScheme, value); }
+        }
+
+        string IHttpRequestFeature.Method
+        {
+            get { return Prop<string>(OwinConstants.RequestMethod); }
+            set { Prop(OwinConstants.RequestMethod, value); }
+        }
+
+        string IHttpRequestFeature.PathBase
+        {
+            get { return Prop<string>(OwinConstants.RequestPathBase); }
+            set { Prop(OwinConstants.RequestPathBase, value); }
+        }
+
+        string IHttpRequestFeature.Path
+        {
+            get { return Prop<string>(OwinConstants.RequestPath); }
+            set { Prop(OwinConstants.RequestPath, value); }
+        }
+
+        string IHttpRequestFeature.QueryString
+        {
+            get { return Utilities.AddQuestionMark(Prop<string>(OwinConstants.RequestQueryString)); }
+            set { Prop(OwinConstants.RequestQueryString, Utilities.RemoveQuestionMark(value)); }
+        }
+
+        string IHttpRequestFeature.RawTarget
+        {
+            get { return string.Empty; }
+            set { throw new NotSupportedException(); }
+        }
+
+        IHeaderDictionary IHttpRequestFeature.Headers
+        {
+            get { return Utilities.MakeHeaderDictionary(Prop<IDictionary<string, string[]>>(OwinConstants.RequestHeaders)); }
+            set { Prop(OwinConstants.RequestHeaders, Utilities.MakeDictionaryStringArray(value)); }
+        }
+
+        string IHttpRequestIdentifierFeature.TraceIdentifier
+        {
+            get { return Prop<string>(OwinConstants.RequestId); }
+            set { Prop(OwinConstants.RequestId, value); }
+        }
+
+        Stream IHttpRequestFeature.Body
+        {
+            get { return Prop<Stream>(OwinConstants.RequestBody); }
+            set { Prop(OwinConstants.RequestBody, value); }
+        }
+
+        int IHttpResponseFeature.StatusCode
+        {
+            get { return Prop<int>(OwinConstants.ResponseStatusCode); }
+            set { Prop(OwinConstants.ResponseStatusCode, value); }
+        }
+
+        string IHttpResponseFeature.ReasonPhrase
+        {
+            get { return Prop<string>(OwinConstants.ResponseReasonPhrase); }
+            set { Prop(OwinConstants.ResponseReasonPhrase, value); }
+        }
+
+        IHeaderDictionary IHttpResponseFeature.Headers
+        {
+            get { return Utilities.MakeHeaderDictionary(Prop<IDictionary<string, string[]>>(OwinConstants.ResponseHeaders)); }
+            set { Prop(OwinConstants.ResponseHeaders, Utilities.MakeDictionaryStringArray(value)); }
+        }
+
+        Stream IHttpResponseFeature.Body
+        {
+            get { return Prop<Stream>(OwinConstants.ResponseBody); }
+            set { Prop(OwinConstants.ResponseBody, value); }
+        }
+
+        bool IHttpResponseFeature.HasStarted
+        {
+            get { return _headersSent; }
+        }
+
+        void IHttpResponseFeature.OnStarting(Func<object, Task> callback, object state)
+        {
+            var register = Prop<Action<Action<object>, object>>(OwinConstants.CommonKeys.OnSendingHeaders);
+            if (register == null)
+            {
+                throw new NotSupportedException(OwinConstants.CommonKeys.OnSendingHeaders);
+            }
+
+            // Need to block on the callback since we can't change the OWIN signature to be async
+            register(s => callback(s).GetAwaiter().GetResult(), state);
+        }
+
+        void IHttpResponseFeature.OnCompleted(Func<object, Task> callback, object state)
+        {
+            throw new NotSupportedException();
+        }
+
+        IPAddress IHttpConnectionFeature.RemoteIpAddress
+        {
+            get { return IPAddress.Parse(Prop<string>(OwinConstants.CommonKeys.RemoteIpAddress)); }
+            set { Prop(OwinConstants.CommonKeys.RemoteIpAddress, value.ToString()); }
+        }
+
+        IPAddress IHttpConnectionFeature.LocalIpAddress
+        {
+            get { return IPAddress.Parse(Prop<string>(OwinConstants.CommonKeys.LocalIpAddress)); }
+            set { Prop(OwinConstants.CommonKeys.LocalIpAddress, value.ToString()); }
+        }
+
+        int IHttpConnectionFeature.RemotePort
+        {
+            get { return int.Parse(Prop<string>(OwinConstants.CommonKeys.RemotePort)); }
+            set { Prop(OwinConstants.CommonKeys.RemotePort, value.ToString(CultureInfo.InvariantCulture)); }
+        }
+
+        int IHttpConnectionFeature.LocalPort
+        {
+            get { return int.Parse(Prop<string>(OwinConstants.CommonKeys.LocalPort)); }
+            set { Prop(OwinConstants.CommonKeys.LocalPort, value.ToString(CultureInfo.InvariantCulture)); }
+        }
+
+        string IHttpConnectionFeature.ConnectionId
+        {
+            get { return Prop<string>(OwinConstants.CommonKeys.ConnectionId); }
+            set { Prop(OwinConstants.CommonKeys.ConnectionId, value); }
+        }
+
+        private bool SupportsSendFile
+        {
+            get
+            {
+                object obj;
+                return Environment.TryGetValue(OwinConstants.SendFiles.SendAsync, out obj) && obj != null;
+            }
+        }
+
+        Task IHttpSendFileFeature.SendFileAsync(string path, long offset, long? length, CancellationToken cancellation)
+        {
+            object obj;
+            if (Environment.TryGetValue(OwinConstants.SendFiles.SendAsync, out obj))
+            {
+                var func = (SendFileFunc)obj;
+                return func(path, offset, length, cancellation);
+            }
+            throw new NotSupportedException(OwinConstants.SendFiles.SendAsync);
+        }
+
+        private bool SupportsClientCerts
+        {
+            get
+            {
+                object obj;
+                if (string.Equals("https", ((IHttpRequestFeature)this).Scheme, StringComparison.OrdinalIgnoreCase)
+                    && (Environment.TryGetValue(OwinConstants.CommonKeys.LoadClientCertAsync, out obj)
+                        || Environment.TryGetValue(OwinConstants.CommonKeys.ClientCertificate, out obj))
+                    && obj != null)
+                {
+                    return true;
+                }
+                return false;
+            }
+        }
+
+        X509Certificate2 ITlsConnectionFeature.ClientCertificate
+        {
+            get { return Prop<X509Certificate2>(OwinConstants.CommonKeys.ClientCertificate); }
+            set { Prop(OwinConstants.CommonKeys.ClientCertificate, value); }
+        }
+
+        async Task<X509Certificate2> ITlsConnectionFeature.GetClientCertificateAsync(CancellationToken cancellationToken)
+        {
+            var loadAsync = Prop<Func<Task>>(OwinConstants.CommonKeys.LoadClientCertAsync);
+            if (loadAsync != null)
+            {
+                await loadAsync();
+            }
+            return Prop<X509Certificate2>(OwinConstants.CommonKeys.ClientCertificate);
+        }
+
+        CancellationToken IHttpRequestLifetimeFeature.RequestAborted
+        {
+            get { return Prop<CancellationToken>(OwinConstants.CallCancelled); }
+            set { Prop(OwinConstants.CallCancelled, value); }
+        }
+
+        void IHttpRequestLifetimeFeature.Abort()
+        {
+            throw new NotImplementedException();
+        }
+
+        ClaimsPrincipal IHttpAuthenticationFeature.User
+        {
+            get
+            {
+                return Prop<ClaimsPrincipal>(OwinConstants.RequestUser)
+                    ?? Utilities.MakeClaimsPrincipal(Prop<IPrincipal>(OwinConstants.Security.User));
+            }
+            set
+            {
+                Prop(OwinConstants.RequestUser, value);
+                Prop(OwinConstants.Security.User, value);
+            }
+        }
+
+        IAuthenticationHandler IHttpAuthenticationFeature.Handler { get; set; }
+
+        /// <summary>
+        /// Gets or sets if the underlying server supports WebSockets. This is enabled by default.
+        /// The value should be consistent across requests.
+        /// </summary>
+        public bool SupportsWebSockets { get; set; }
+
+        bool IHttpWebSocketFeature.IsWebSocketRequest
+        {
+            get
+            {
+                object obj;
+                return Environment.TryGetValue(OwinConstants.WebSocket.AcceptAlt, out obj);
+            }
+        }
+
+        Task<WebSocket> IHttpWebSocketFeature.AcceptAsync(WebSocketAcceptContext context)
+        {
+            object obj;
+            if (!Environment.TryGetValue(OwinConstants.WebSocket.AcceptAlt, out obj))
+            {
+                throw new NotSupportedException("WebSockets are not supported"); // TODO: LOC
+            }
+            var accept = (Func<WebSocketAcceptContext, Task<WebSocket>>)obj;
+            return accept(context);
+        }
+
+        // IFeatureCollection
+
+        public int Revision
+        {
+            get { return 0; } // Not modifiable
+        }
+
+        public bool IsReadOnly
+        {
+            get { return true; }
+        }
+
+        public object this[Type key]
+        {
+            get { return Get(key); }
+            set { throw new NotSupportedException(); }
+        }
+
+        private bool SupportsInterface(Type key)
+        {
+            // Does this type implement the requested interface?
+            if (key.GetTypeInfo().IsAssignableFrom(GetType().GetTypeInfo()))
+            {
+                // Check for conditional features
+                if (key == typeof(IHttpSendFileFeature))
+                {
+                    return SupportsSendFile;
+                }
+                else if (key == typeof(ITlsConnectionFeature))
+                {
+                    return SupportsClientCerts;
+                }
+                else if (key == typeof(IHttpWebSocketFeature))
+                {
+                    return SupportsWebSockets;
+                }
+
+                // The rest of the features are always supported.
+                return true;
+            }
+            return false;
+        }
+
+        public object Get(Type key)
+        {
+            if (SupportsInterface(key))
+            {
+                return this;
+            }
+            return null;
+        }
+
+        public void Set(Type key, object value)
+        {
+            throw new NotSupportedException();
+        }
+
+        public TFeature Get<TFeature>()
+        {
+            return (TFeature)this[typeof(TFeature)];
+        }
+
+        public void Set<TFeature>(TFeature instance)
+        {
+            this[typeof(TFeature)] = instance;
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        public IEnumerator<KeyValuePair<Type, object>> GetEnumerator()
+        {
+            yield return new KeyValuePair<Type, object>(typeof(IHttpRequestFeature), this);
+            yield return new KeyValuePair<Type, object>(typeof(IHttpResponseFeature), this);
+            yield return new KeyValuePair<Type, object>(typeof(IHttpConnectionFeature), this);
+            yield return new KeyValuePair<Type, object>(typeof(IHttpRequestIdentifierFeature), this);
+            yield return new KeyValuePair<Type, object>(typeof(IHttpRequestLifetimeFeature), this);
+            yield return new KeyValuePair<Type, object>(typeof(IHttpAuthenticationFeature), this);
+            yield return new KeyValuePair<Type, object>(typeof(IOwinEnvironmentFeature), this);
+
+            // Check for conditional features
+            if (SupportsSendFile)
+            {
+                yield return new KeyValuePair<Type, object>(typeof(IHttpSendFileFeature), this);
+            }
+            if (SupportsClientCerts)
+            {
+                yield return new KeyValuePair<Type, object>(typeof(ITlsConnectionFeature), this);
+            }
+            if (SupportsWebSockets)
+            {
+                yield return new KeyValuePair<Type, object>(typeof(IHttpWebSocketFeature), this);
+            }
+        }
+
+        public void Dispose()
+        {
+        }
+    }
+}
+
diff --git a/src/Http/Owin/src/Utilities.cs b/src/Http/Owin/src/Utilities.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b65cae78a9fca3d7b0fcd1d5da0c540ab0817d4e
--- /dev/null
+++ b/src/Http/Owin/src/Utilities.cs
@@ -0,0 +1,69 @@
+// 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.Security.Claims;
+using System.Security.Principal;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.Owin
+{
+    internal static class Utilities
+    {
+        internal static string RemoveQuestionMark(string queryString)
+        {
+            if (!string.IsNullOrEmpty(queryString))
+            {
+                if (queryString[0] == '?')
+                {
+                    return queryString.Substring(1);
+                }
+            }
+            return queryString;
+        }
+
+        internal static string AddQuestionMark(string queryString)
+        {
+            if (!string.IsNullOrEmpty(queryString))
+            {
+                return '?' + queryString;
+            }
+            return queryString;
+        }
+
+        internal static ClaimsPrincipal MakeClaimsPrincipal(IPrincipal principal)
+        {
+            if (principal == null)
+            {
+                return null;
+            }
+            if (principal is ClaimsPrincipal)
+            {
+                return principal as ClaimsPrincipal;
+            }
+            return new ClaimsPrincipal(principal);
+        }
+
+        internal static IHeaderDictionary MakeHeaderDictionary(IDictionary<string, string[]> dictionary)
+        {
+            var wrapper = dictionary as DictionaryStringArrayWrapper;
+            if (wrapper != null)
+            {
+                return wrapper.Inner;
+            }
+            return new DictionaryStringValuesWrapper(dictionary);
+        }
+
+        internal static IDictionary<string, string[]> MakeDictionaryStringArray(IHeaderDictionary dictionary)
+        {
+            var wrapper = dictionary as DictionaryStringValuesWrapper;
+            if (wrapper != null)
+            {
+                return wrapper.Inner;
+            }
+            return new DictionaryStringArrayWrapper(dictionary);
+        }
+    }
+}
diff --git a/src/Http/Owin/src/WebSockets/OwinWebSocketAcceptAdapter.cs b/src/Http/Owin/src/WebSockets/OwinWebSocketAcceptAdapter.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5fe43dedd288edf4284a1a1ee7ce85eb0fa1393c
--- /dev/null
+++ b/src/Http/Owin/src/WebSockets/OwinWebSocketAcceptAdapter.cs
@@ -0,0 +1,143 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Net.WebSockets;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Owin
+{
+    using AppFunc = Func<IDictionary<string, object>, Task>;
+    using WebSocketAccept =
+        Action
+        <
+            IDictionary<string, object>, // WebSocket Accept parameters
+            Func // WebSocketFunc callback
+            <
+                IDictionary<string, object>, // WebSocket environment
+                Task // Complete
+            >
+        >;
+    using WebSocketAcceptAlt =
+        Func
+        <
+            WebSocketAcceptContext, // WebSocket Accept parameters
+            Task<WebSocket>
+        >;
+
+    /// <summary>
+    /// This adapts the OWIN WebSocket accept flow to match the ASP.NET Core WebSocket Accept flow.
+    /// This enables ASP.NET Core components to use WebSockets on OWIN based servers.
+    /// </summary>
+    public class OwinWebSocketAcceptAdapter
+    {
+        private WebSocketAccept _owinWebSocketAccept;
+        private TaskCompletionSource<int> _requestTcs = new TaskCompletionSource<int>();
+        private TaskCompletionSource<WebSocket> _acceptTcs = new TaskCompletionSource<WebSocket>();
+        private TaskCompletionSource<int> _upstreamWentAsync = new TaskCompletionSource<int>();
+        private string _subProtocol = null;
+
+        private OwinWebSocketAcceptAdapter(WebSocketAccept owinWebSocketAccept)
+        {
+            _owinWebSocketAccept = owinWebSocketAccept;
+        }
+
+        private Task RequestTask { get { return _requestTcs.Task; } }
+        private Task UpstreamTask { get; set; }
+        private TaskCompletionSource<int> UpstreamWentAsyncTcs { get { return _upstreamWentAsync; } }
+
+        private async Task<WebSocket> AcceptWebSocketAsync(WebSocketAcceptContext context)
+        {
+            IDictionary<string, object> options = null;
+            if (context is OwinWebSocketAcceptContext)
+            {
+                var acceptContext = context as OwinWebSocketAcceptContext;
+                options = acceptContext.Options;
+                _subProtocol = acceptContext.SubProtocol;
+            }
+            else if (context?.SubProtocol != null)
+            {
+                options = new Dictionary<string, object>(1)
+                {
+                    { OwinConstants.WebSocket.SubProtocol, context.SubProtocol }
+                };
+                _subProtocol = context.SubProtocol;
+            }
+
+            // Accept may have been called synchronously on the original request thread, we might not have a task yet. Go async.
+            await _upstreamWentAsync.Task;
+
+            _owinWebSocketAccept(options, OwinAcceptCallback);
+            _requestTcs.TrySetResult(0); // Let the pipeline unwind.
+
+            return await _acceptTcs.Task;
+        }
+
+        private Task OwinAcceptCallback(IDictionary<string, object> webSocketContext)
+        {
+            _acceptTcs.TrySetResult(new OwinWebSocketAdapter(webSocketContext, _subProtocol));
+            return UpstreamTask;
+        }
+
+        // Make sure declined websocket requests complete. This is a no-op for accepted websocket requests.
+        private void EnsureCompleted(Task task)
+        {
+            if (task.IsCanceled)
+            {
+                _requestTcs.TrySetCanceled();
+            }
+            else if (task.IsFaulted)
+            {
+                _requestTcs.TrySetException(task.Exception);
+            }
+            else
+            {
+                _requestTcs.TrySetResult(0);
+            }
+        }
+
+        // Order of operations:
+        // 1. A WebSocket handshake request is received by the middleware.
+        // 2. The middleware inserts an alternate Accept signature into the OWIN environment.
+        // 3. The middleware invokes Next and stores Next's Task locally. It then returns an alternate Task to the server.
+        // 4. The OwinFeatureCollection adapts the alternate Accept signature to IHttpWebSocketFeature.AcceptAsync.
+        // 5. A component later in the pipleline invokes IHttpWebSocketFeature.AcceptAsync (mapped to AcceptWebSocketAsync).
+        // 6. The middleware calls the OWIN Accept, providing a local callback, and returns an incomplete Task.
+        // 7. The middleware completes the alternate Task it returned from Invoke, telling the server that the request pipeline has completed.
+        // 8. The server invokes the middleware's callback, which creats a WebSocket adapter complete's the orriginal Accept Task with it.
+        // 9. The middleware waits while the application uses the WebSocket, where the end is signaled by the Next's Task completion.
+        public static AppFunc AdaptWebSockets(AppFunc next)
+        {
+            return environment =>
+            {
+                object accept;
+                if (environment.TryGetValue(OwinConstants.WebSocket.Accept, out accept) && accept is WebSocketAccept)
+                {
+                    var adapter = new OwinWebSocketAcceptAdapter((WebSocketAccept)accept);
+
+                    environment[OwinConstants.WebSocket.AcceptAlt] = new WebSocketAcceptAlt(adapter.AcceptWebSocketAsync);
+
+                    try
+                    {
+                        adapter.UpstreamTask = next(environment);
+                        adapter.UpstreamWentAsyncTcs.TrySetResult(0);
+                        adapter.UpstreamTask.ContinueWith(adapter.EnsureCompleted, TaskContinuationOptions.ExecuteSynchronously);
+                    }
+                    catch (Exception ex)
+                    {
+                        adapter.UpstreamWentAsyncTcs.TrySetException(ex);
+                        throw;
+                    }
+
+                    return adapter.RequestTask;
+                }
+                else
+                {
+                    return next(environment);
+                }
+            };
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Owin/src/WebSockets/OwinWebSocketAcceptContext.cs b/src/Http/Owin/src/WebSockets/OwinWebSocketAcceptContext.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a9fd28edbae8c81f3b3a6cb06f588927f7b7ca2e
--- /dev/null
+++ b/src/Http/Owin/src/WebSockets/OwinWebSocketAcceptContext.cs
@@ -0,0 +1,48 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Owin
+{
+    public class OwinWebSocketAcceptContext : WebSocketAcceptContext
+    {
+        private IDictionary<string, object> _options;
+
+        public OwinWebSocketAcceptContext() : this(new Dictionary<string, object>(1))
+        {
+        }
+
+        public OwinWebSocketAcceptContext(IDictionary<string, object> options)
+        {
+            _options = options;
+        }
+
+        public override string SubProtocol
+        {
+            get
+            {
+                object obj;
+                if (_options != null && _options.TryGetValue(OwinConstants.WebSocket.SubProtocol, out obj))
+                {
+                    return (string)obj;
+                }
+                return null;
+            }
+            set
+            {
+                if (_options == null)
+                {
+                    _options = new Dictionary<string, object>(1);
+                }
+                _options[OwinConstants.WebSocket.SubProtocol] = value;
+            }
+        }
+
+        public IDictionary<string, object> Options
+        {
+            get { return _options; }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Owin/src/WebSockets/OwinWebSocketAdapter.cs b/src/Http/Owin/src/WebSockets/OwinWebSocketAdapter.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e7eed159ea370032e950199a1df732b0b806d370
--- /dev/null
+++ b/src/Http/Owin/src/WebSockets/OwinWebSocketAdapter.cs
@@ -0,0 +1,200 @@
+// 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.Buffers;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Net.WebSockets;
+
+namespace Microsoft.AspNetCore.Owin
+{
+    // http://owin.org/extensions/owin-WebSocket-Extension-v0.4.0.htm
+    using WebSocketCloseAsync =
+        Func<int /* closeStatus */,
+            string /* closeDescription */,
+            CancellationToken /* cancel */,
+            Task>;
+    using WebSocketReceiveAsync =
+        Func<ArraySegment<byte> /* data */,
+            CancellationToken /* cancel */,
+            Task<Tuple<int /* messageType */,
+                bool /* endOfMessage */,
+                int /* count */>>>;
+    using WebSocketSendAsync =
+        Func<ArraySegment<byte> /* data */,
+            int /* messageType */,
+            bool /* endOfMessage */,
+            CancellationToken /* cancel */,
+            Task>;
+    using RawWebSocketReceiveResult = Tuple<int, // type
+        bool, // end of message?
+        int>; // count
+
+    public class OwinWebSocketAdapter : WebSocket
+    {
+        private const int _rentedBufferSize = 1024;
+        private IDictionary<string, object> _websocketContext;
+        private WebSocketSendAsync _sendAsync;
+        private WebSocketReceiveAsync _receiveAsync;
+        private WebSocketCloseAsync _closeAsync;
+        private WebSocketState _state;
+        private string _subProtocol;
+
+        public OwinWebSocketAdapter(IDictionary<string, object> websocketContext, string subProtocol)
+        {
+            _websocketContext = websocketContext;
+            _sendAsync = (WebSocketSendAsync)websocketContext[OwinConstants.WebSocket.SendAsync];
+            _receiveAsync = (WebSocketReceiveAsync)websocketContext[OwinConstants.WebSocket.ReceiveAsync];
+            _closeAsync = (WebSocketCloseAsync)websocketContext[OwinConstants.WebSocket.CloseAsync];
+            _state = WebSocketState.Open;
+            _subProtocol = subProtocol;
+        }
+
+        public override WebSocketCloseStatus? CloseStatus
+        {
+            get
+            {
+                object obj;
+                if (_websocketContext.TryGetValue(OwinConstants.WebSocket.ClientCloseStatus, out obj))
+                {
+                    return (WebSocketCloseStatus)obj;
+                }
+                return null;
+            }
+        }
+
+        public override string CloseStatusDescription
+        {
+            get
+            {
+                object obj;
+                if (_websocketContext.TryGetValue(OwinConstants.WebSocket.ClientCloseDescription, out obj))
+                {
+                    return (string)obj;
+                }
+                return null;
+            }
+        }
+
+        public override string SubProtocol
+        {
+            get
+            {
+                return _subProtocol;
+            }
+        }
+
+        public override WebSocketState State
+        {
+            get
+            {
+                return _state;
+            }
+        }
+
+        public override async Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken)
+        {
+            var rawResult = await _receiveAsync(buffer, cancellationToken);
+            var messageType = OpCodeToEnum(rawResult.Item1);
+            if (messageType == WebSocketMessageType.Close)
+            {
+                if (State == WebSocketState.Open)
+                {
+                    _state = WebSocketState.CloseReceived;
+                }
+                else if (State == WebSocketState.CloseSent)
+                {
+                    _state = WebSocketState.Closed;
+                }
+                return new WebSocketReceiveResult(rawResult.Item3, messageType, rawResult.Item2, CloseStatus, CloseStatusDescription);
+            }
+            else
+            {
+                return new WebSocketReceiveResult(rawResult.Item3, messageType, rawResult.Item2);
+            }
+        }
+
+        public override Task SendAsync(ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken)
+        {
+            return _sendAsync(buffer, EnumToOpCode(messageType), endOfMessage, cancellationToken);
+        }
+
+        public override async Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
+        {
+            if (State == WebSocketState.Open || State == WebSocketState.CloseReceived)
+            {
+                await CloseOutputAsync(closeStatus, statusDescription, cancellationToken);
+            }
+
+            var buffer = ArrayPool<byte>.Shared.Rent(_rentedBufferSize);
+            try
+            {
+                while (State == WebSocketState.CloseSent)
+                {
+                    // Drain until close received
+                    await ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
+                }
+            }
+            finally
+            {
+                ArrayPool<byte>.Shared.Return(buffer);
+            }
+        }
+
+        public override Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
+        {
+            // TODO: Validate state
+            if (State == WebSocketState.Open)
+            {
+                _state = WebSocketState.CloseSent;
+            }
+            else if (State == WebSocketState.CloseReceived)
+            {
+                _state = WebSocketState.Closed;
+            }
+            return _closeAsync((int)closeStatus, statusDescription, cancellationToken);
+        }
+
+        public override void Abort()
+        {
+            _state = WebSocketState.Aborted;
+        }
+
+        public override void Dispose()
+        {
+            _state = WebSocketState.Closed;
+        }
+
+        private static WebSocketMessageType OpCodeToEnum(int messageType)
+        {
+            switch (messageType)
+            {
+                case 0x1:
+                    return WebSocketMessageType.Text;
+                case 0x2:
+                    return WebSocketMessageType.Binary;
+                case 0x8:
+                    return WebSocketMessageType.Close;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(messageType), messageType, string.Empty);
+            }
+        }
+
+        private static int EnumToOpCode(WebSocketMessageType webSocketMessageType)
+        {
+            switch (webSocketMessageType)
+            {
+                case WebSocketMessageType.Text:
+                    return 0x1;
+                case WebSocketMessageType.Binary:
+                    return 0x2;
+                case WebSocketMessageType.Close:
+                    return 0x8;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(webSocketMessageType), webSocketMessageType, string.Empty);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Owin/src/WebSockets/WebSocketAcceptAdapter.cs b/src/Http/Owin/src/WebSockets/WebSocketAcceptAdapter.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f1355da4c247449eef621a2279e7522e219662f5
--- /dev/null
+++ b/src/Http/Owin/src/WebSockets/WebSocketAcceptAdapter.cs
@@ -0,0 +1,92 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Net.WebSockets;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Microsoft.AspNetCore.Owin
+{
+    using AppFunc = Func<IDictionary<string, object>, Task>;
+    using WebSocketAccept =
+        Action
+        <
+            IDictionary<string, object>, // WebSocket Accept parameters
+            Func // WebSocketFunc callback
+            <
+                IDictionary<string, object>, // WebSocket environment
+                Task // Complete
+            >
+        >;
+    using WebSocketAcceptAlt =
+        Func
+        <
+            WebSocketAcceptContext, // WebSocket Accept parameters
+            Task<WebSocket>
+        >;
+
+    /// <summary>
+    /// This adapts the ASP.NET Core WebSocket Accept flow to match the OWIN WebSocket accept flow.
+    /// This enables OWIN based components to use WebSockets on ASP.NET Core servers.
+    /// </summary>
+    public class WebSocketAcceptAdapter
+    {
+        private IDictionary<string, object> _env;
+        private WebSocketAcceptAlt _accept;
+        private AppFunc _callback;
+        private IDictionary<string, object> _options;
+
+        public WebSocketAcceptAdapter(IDictionary<string, object> env, WebSocketAcceptAlt accept)
+	    {
+            _env = env;
+            _accept = accept;
+        }
+
+        private void AcceptWebSocket(IDictionary<string, object> options, AppFunc callback)
+        {
+            _options = options;
+            _callback = callback;
+            _env[OwinConstants.ResponseStatusCode] = 101;
+        }
+
+        public static AppFunc AdaptWebSockets(AppFunc next)
+        {
+            return async environment =>
+            {
+                object accept;
+                if (environment.TryGetValue(OwinConstants.WebSocket.AcceptAlt, out accept) && accept is WebSocketAcceptAlt)
+                {
+                    var adapter = new WebSocketAcceptAdapter(environment, (WebSocketAcceptAlt)accept);
+
+                    environment[OwinConstants.WebSocket.Accept] = new WebSocketAccept(adapter.AcceptWebSocket);
+                    await next(environment);
+                    if ((int)environment[OwinConstants.ResponseStatusCode] == 101 && adapter._callback != null)
+                    {
+                        WebSocketAcceptContext acceptContext = null;
+                        object obj;
+                        if (adapter._options != null && adapter._options.TryGetValue(typeof(WebSocketAcceptContext).FullName, out obj))
+                        {
+                            acceptContext = obj as WebSocketAcceptContext;
+                        }
+                        else if (adapter._options != null)
+                        {
+                            acceptContext = new OwinWebSocketAcceptContext(adapter._options);
+                        }
+
+                        var webSocket = await adapter._accept(acceptContext);
+                        var webSocketAdapter = new WebSocketAdapter(webSocket, (CancellationToken)environment[OwinConstants.CallCancelled]);
+                        await adapter._callback(webSocketAdapter.Environment);
+                        await webSocketAdapter.CleanupAsync();
+                    }
+                }
+                else
+                {
+                    await next(environment);
+                }
+            };
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/Owin/src/WebSockets/WebSocketAdapter.cs b/src/Http/Owin/src/WebSockets/WebSocketAdapter.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7fad98704cfee01f0d194893d8e90fc4955915a5
--- /dev/null
+++ b/src/Http/Owin/src/WebSockets/WebSocketAdapter.cs
@@ -0,0 +1,171 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Net.WebSockets;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Owin
+{
+    using WebSocketCloseAsync =
+        Func<int /* closeStatus */,
+            string /* closeDescription */,
+            CancellationToken /* cancel */,
+            Task>;
+    using WebSocketReceiveAsync =
+        Func<ArraySegment<byte> /* data */,
+            CancellationToken /* cancel */,
+            Task<Tuple<int /* messageType */,
+                bool /* endOfMessage */,
+                int /* count */>>>;
+    using WebSocketReceiveTuple =
+        Tuple<int /* messageType */,
+            bool /* endOfMessage */,
+            int /* count */>;
+    using WebSocketSendAsync =
+        Func<ArraySegment<byte> /* data */,
+            int /* messageType */,
+            bool /* endOfMessage */,
+            CancellationToken /* cancel */,
+            Task>;
+
+    public class WebSocketAdapter
+    {
+        private readonly WebSocket _webSocket;
+        private readonly IDictionary<string, object> _environment;
+        private readonly CancellationToken _cancellationToken;
+
+        internal WebSocketAdapter(WebSocket webSocket, CancellationToken ct)
+        {
+            _webSocket = webSocket;
+            _cancellationToken = ct;
+
+            _environment = new Dictionary<string, object>();
+            _environment[OwinConstants.WebSocket.SendAsync] = new WebSocketSendAsync(SendAsync);
+            _environment[OwinConstants.WebSocket.ReceiveAsync] = new WebSocketReceiveAsync(ReceiveAsync);
+            _environment[OwinConstants.WebSocket.CloseAsync] = new WebSocketCloseAsync(CloseAsync);
+            _environment[OwinConstants.WebSocket.CallCancelled] = ct;
+            _environment[OwinConstants.WebSocket.Version] = OwinConstants.WebSocket.VersionValue;
+
+            _environment[typeof(WebSocket).FullName] = webSocket;
+        }
+
+        internal IDictionary<string, object> Environment
+        {
+            get { return _environment; }
+        }
+
+        internal Task SendAsync(ArraySegment<byte> buffer, int messageType, bool endOfMessage, CancellationToken cancel)
+        {
+            // Remap close messages to CloseAsync.  System.Net.WebSockets.WebSocket.SendAsync does not allow close messages.
+            if (messageType == 0x8)
+            {
+                return RedirectSendToCloseAsync(buffer, cancel);
+            }
+            else if (messageType == 0x9 || messageType == 0xA)
+            {
+                // Ping & Pong, not allowed by the underlying APIs, silently discard.
+                return Task.CompletedTask;
+            }
+
+            return _webSocket.SendAsync(buffer, OpCodeToEnum(messageType), endOfMessage, cancel);
+        }
+
+        internal async Task<WebSocketReceiveTuple> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancel)
+        {
+            WebSocketReceiveResult nativeResult = await _webSocket.ReceiveAsync(buffer, cancel);
+
+            if (nativeResult.MessageType == WebSocketMessageType.Close)
+            {
+                _environment[OwinConstants.WebSocket.ClientCloseStatus] = (int)(nativeResult.CloseStatus ?? WebSocketCloseStatus.NormalClosure);
+                _environment[OwinConstants.WebSocket.ClientCloseDescription] = nativeResult.CloseStatusDescription ?? string.Empty;
+            }
+
+            return new WebSocketReceiveTuple(
+                EnumToOpCode(nativeResult.MessageType),
+                nativeResult.EndOfMessage,
+                nativeResult.Count);
+        }
+
+        internal Task CloseAsync(int status, string description, CancellationToken cancel)
+        {
+            return _webSocket.CloseOutputAsync((WebSocketCloseStatus)status, description, cancel);
+        }
+
+        private Task RedirectSendToCloseAsync(ArraySegment<byte> buffer, CancellationToken cancel)
+        {
+            if (buffer.Array == null || buffer.Count == 0)
+            {
+                return CloseAsync(1000, string.Empty, cancel);
+            }
+            else if (buffer.Count >= 2)
+            {
+                // Unpack the close message.
+                int statusCode =
+                    (buffer.Array[buffer.Offset] << 8)
+                        | buffer.Array[buffer.Offset + 1];
+                string description = Encoding.UTF8.GetString(buffer.Array, buffer.Offset + 2, buffer.Count - 2);
+
+                return CloseAsync(statusCode, description, cancel);
+            }
+            else
+            {
+                throw new ArgumentOutOfRangeException(nameof(buffer));
+            }
+        }
+
+        internal async Task CleanupAsync()
+        {
+            switch (_webSocket.State)
+            {
+                case WebSocketState.Closed: // Closed gracefully, no action needed. 
+                case WebSocketState.Aborted: // Closed abortively, no action needed.                       
+                    break;
+                case WebSocketState.CloseReceived:
+                    // Echo what the client said, if anything.
+                    await _webSocket.CloseAsync(_webSocket.CloseStatus ?? WebSocketCloseStatus.NormalClosure,
+                        _webSocket.CloseStatusDescription ?? string.Empty, _cancellationToken);
+                    break;
+                case WebSocketState.Open:
+                case WebSocketState.CloseSent: // No close received, abort so we don't have to drain the pipe.
+                    _webSocket.Abort();
+                    break;
+                default:
+                    throw new NotSupportedException($"Unsupported {nameof(WebSocketState)} value: {_webSocket.State}.");
+            }
+        }
+
+        private static WebSocketMessageType OpCodeToEnum(int messageType)
+        {
+            switch (messageType)
+            {
+                case 0x1:
+                    return WebSocketMessageType.Text;
+                case 0x2:
+                    return WebSocketMessageType.Binary;
+                case 0x8:
+                    return WebSocketMessageType.Close;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(messageType), messageType, string.Empty);
+            }
+        }
+
+        private static int EnumToOpCode(WebSocketMessageType webSocketMessageType)
+        {
+            switch (webSocketMessageType)
+            {
+                case WebSocketMessageType.Text:
+                    return 0x1;
+                case WebSocketMessageType.Binary:
+                    return 0x2;
+                case WebSocketMessageType.Close:
+                    return 0x8;
+                default:
+                    throw new ArgumentOutOfRangeException(nameof(webSocketMessageType), webSocketMessageType, string.Empty);
+            }
+        }
+    }
+}
diff --git a/src/Http/Owin/src/baseline.netcore.json b/src/Http/Owin/src/baseline.netcore.json
new file mode 100644
index 0000000000000000000000000000000000000000..8211307418048e28f73f4491d63c31b97fbe734d
--- /dev/null
+++ b/src/Http/Owin/src/baseline.netcore.json
@@ -0,0 +1,1010 @@
+{
+  "AssemblyIdentity": "Microsoft.AspNetCore.Owin, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+  "Types": [
+    {
+      "Name": "Microsoft.AspNetCore.Builder.OwinExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "UseOwin",
+          "Parameters": [
+            {
+              "Name": "builder",
+              "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+            }
+          ],
+          "ReturnType": "System.Action<System.Func<System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>, System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>>>",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseOwin",
+          "Parameters": [
+            {
+              "Name": "builder",
+              "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder"
+            },
+            {
+              "Name": "pipeline",
+              "Type": "System.Action<System.Action<System.Func<System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>, System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>>>>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseBuilder",
+          "Parameters": [
+            {
+              "Name": "app",
+              "Type": "System.Action<System.Func<System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>, System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>>>"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseBuilder",
+          "Parameters": [
+            {
+              "Name": "app",
+              "Type": "System.Action<System.Func<System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>, System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>>>"
+            },
+            {
+              "Name": "serviceProvider",
+              "Type": "System.IServiceProvider"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseBuilder",
+          "Parameters": [
+            {
+              "Name": "app",
+              "Type": "System.Action<System.Func<System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>, System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>>>"
+            },
+            {
+              "Name": "pipeline",
+              "Type": "System.Action<Microsoft.AspNetCore.Builder.IApplicationBuilder>"
+            }
+          ],
+          "ReturnType": "System.Action<System.Func<System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>, System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>>>",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "UseBuilder",
+          "Parameters": [
+            {
+              "Name": "app",
+              "Type": "System.Action<System.Func<System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>, System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>>>"
+            },
+            {
+              "Name": "pipeline",
+              "Type": "System.Action<Microsoft.AspNetCore.Builder.IApplicationBuilder>"
+            },
+            {
+              "Name": "serviceProvider",
+              "Type": "System.IServiceProvider"
+            }
+          ],
+          "ReturnType": "System.Action<System.Func<System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>, System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>>>",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Owin.IOwinEnvironmentFeature",
+      "Visibility": "Public",
+      "Kind": "Interface",
+      "Abstract": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Environment",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.String, System.Object>",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Environment",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Collections.Generic.IDictionary<System.String, System.Object>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Owin.OwinEnvironment",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "System.Collections.Generic.IDictionary<System.String, System.Object>"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "GetEnumerator",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<System.String, System.Object>>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.String, System.Object>>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_FeatureMaps",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.String, Microsoft.AspNetCore.Owin.OwinEnvironment+FeatureMap>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "context",
+              "Type": "Microsoft.AspNetCore.Http.HttpContext"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Owin.OwinEnvironmentFeature",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Owin.IOwinEnvironmentFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Environment",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.String, System.Object>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Owin.IOwinEnvironmentFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Environment",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Collections.Generic.IDictionary<System.String, System.Object>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Owin.IOwinEnvironmentFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Owin.OwinFeatureCollection",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+        "Microsoft.AspNetCore.Http.Features.IHttpRequestFeature",
+        "Microsoft.AspNetCore.Http.Features.IHttpResponseFeature",
+        "Microsoft.AspNetCore.Http.Features.IHttpConnectionFeature",
+        "Microsoft.AspNetCore.Http.Features.IHttpSendFileFeature",
+        "Microsoft.AspNetCore.Http.Features.ITlsConnectionFeature",
+        "Microsoft.AspNetCore.Http.Features.IHttpRequestIdentifierFeature",
+        "Microsoft.AspNetCore.Http.Features.IHttpRequestLifetimeFeature",
+        "Microsoft.AspNetCore.Http.Features.Authentication.IHttpAuthenticationFeature",
+        "Microsoft.AspNetCore.Http.Features.IHttpWebSocketFeature",
+        "Microsoft.AspNetCore.Owin.IOwinEnvironmentFeature"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "GetEnumerator",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<System.Type, System.Object>>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.Type, System.Object>>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Environment",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.String, System.Object>",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Owin.IOwinEnvironmentFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Environment",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Collections.Generic.IDictionary<System.String, System.Object>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Owin.IOwinEnvironmentFeature",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_SupportsWebSockets",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_SupportsWebSockets",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Revision",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_IsReadOnly",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Item",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.Type"
+            }
+          ],
+          "ReturnType": "System.Object",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Item",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.Type"
+            },
+            {
+              "Name": "value",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Get",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.Type"
+            }
+          ],
+          "ReturnType": "System.Object",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Set",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.Type"
+            },
+            {
+              "Name": "value",
+              "Type": "System.Object"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Get<T0>",
+          "Parameters": [],
+          "ReturnType": "T0",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "TFeature",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Method",
+          "Name": "Set<T0>",
+          "Parameters": [
+            {
+              "Name": "instance",
+              "Type": "T0"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "Microsoft.AspNetCore.Http.Features.IFeatureCollection",
+          "Visibility": "Public",
+          "GenericParameter": [
+            {
+              "ParameterName": "TFeature",
+              "ParameterPosition": 0,
+              "BaseTypeOrInterfaces": []
+            }
+          ]
+        },
+        {
+          "Kind": "Method",
+          "Name": "Dispose",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "environment",
+              "Type": "System.Collections.Generic.IDictionary<System.String, System.Object>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Owin.OwinWebSocketAcceptAdapter",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "AdaptWebSockets",
+          "Parameters": [
+            {
+              "Name": "next",
+              "Type": "System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>"
+            }
+          ],
+          "ReturnType": "System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Owin.OwinWebSocketAcceptContext",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "BaseType": "Microsoft.AspNetCore.Http.WebSocketAcceptContext",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_SubProtocol",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_SubProtocol",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Options",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.IDictionary<System.String, System.Object>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "options",
+              "Type": "System.Collections.Generic.IDictionary<System.String, System.Object>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Owin.OwinWebSocketAdapter",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "BaseType": "System.Net.WebSockets.WebSocket",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Dispose",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "ImplementedInterface": "System.IDisposable",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_CloseStatus",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Net.WebSockets.WebSocketCloseStatus>",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_CloseStatusDescription",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_SubProtocol",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_State",
+          "Parameters": [],
+          "ReturnType": "System.Net.WebSockets.WebSocketState",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ReceiveAsync",
+          "Parameters": [
+            {
+              "Name": "buffer",
+              "Type": "System.ArraySegment<System.Byte>"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Net.WebSockets.WebSocketReceiveResult>",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SendAsync",
+          "Parameters": [
+            {
+              "Name": "buffer",
+              "Type": "System.ArraySegment<System.Byte>"
+            },
+            {
+              "Name": "messageType",
+              "Type": "System.Net.WebSockets.WebSocketMessageType"
+            },
+            {
+              "Name": "endOfMessage",
+              "Type": "System.Boolean"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "CloseAsync",
+          "Parameters": [
+            {
+              "Name": "closeStatus",
+              "Type": "System.Net.WebSockets.WebSocketCloseStatus"
+            },
+            {
+              "Name": "statusDescription",
+              "Type": "System.String"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "CloseOutputAsync",
+          "Parameters": [
+            {
+              "Name": "closeStatus",
+              "Type": "System.Net.WebSockets.WebSocketCloseStatus"
+            },
+            {
+              "Name": "statusDescription",
+              "Type": "System.String"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Abort",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "websocketContext",
+              "Type": "System.Collections.Generic.IDictionary<System.String, System.Object>"
+            },
+            {
+              "Name": "subProtocol",
+              "Type": "System.String"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Owin.WebSocketAcceptAdapter",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "AdaptWebSockets",
+          "Parameters": [
+            {
+              "Name": "next",
+              "Type": "System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>"
+            }
+          ],
+          "ReturnType": "System.Func<System.Collections.Generic.IDictionary<System.String, System.Object>, System.Threading.Tasks.Task>",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "env",
+              "Type": "System.Collections.Generic.IDictionary<System.String, System.Object>"
+            },
+            {
+              "Name": "accept",
+              "Type": "System.Func<Microsoft.AspNetCore.Http.WebSocketAcceptContext, System.Threading.Tasks.Task<System.Net.WebSockets.WebSocket>>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Owin.WebSocketAdapter",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Owin.OwinEnvironment+FeatureMap",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_CanSet",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "featureInterface",
+              "Type": "System.Type"
+            },
+            {
+              "Name": "getter",
+              "Type": "System.Func<System.Object, System.Object>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "featureInterface",
+              "Type": "System.Type"
+            },
+            {
+              "Name": "getter",
+              "Type": "System.Func<System.Object, System.Object>"
+            },
+            {
+              "Name": "defaultFactory",
+              "Type": "System.Func<System.Object>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "featureInterface",
+              "Type": "System.Type"
+            },
+            {
+              "Name": "getter",
+              "Type": "System.Func<System.Object, System.Object>"
+            },
+            {
+              "Name": "setter",
+              "Type": "System.Action<System.Object, System.Object>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "featureInterface",
+              "Type": "System.Type"
+            },
+            {
+              "Name": "getter",
+              "Type": "System.Func<System.Object, System.Object>"
+            },
+            {
+              "Name": "defaultFactory",
+              "Type": "System.Func<System.Object>"
+            },
+            {
+              "Name": "setter",
+              "Type": "System.Action<System.Object, System.Object>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "featureInterface",
+              "Type": "System.Type"
+            },
+            {
+              "Name": "getter",
+              "Type": "System.Func<System.Object, System.Object>"
+            },
+            {
+              "Name": "defaultFactory",
+              "Type": "System.Func<System.Object>"
+            },
+            {
+              "Name": "setter",
+              "Type": "System.Action<System.Object, System.Object>"
+            },
+            {
+              "Name": "featureFactory",
+              "Type": "System.Func<System.Object>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.Owin.OwinEnvironment+FeatureMap<T0>",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "BaseType": "Microsoft.AspNetCore.Owin.OwinEnvironment+FeatureMap",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "getter",
+              "Type": "System.Func<T0, System.Object>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "getter",
+              "Type": "System.Func<T0, System.Object>"
+            },
+            {
+              "Name": "defaultFactory",
+              "Type": "System.Func<System.Object>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "getter",
+              "Type": "System.Func<T0, System.Object>"
+            },
+            {
+              "Name": "setter",
+              "Type": "System.Action<T0, System.Object>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "getter",
+              "Type": "System.Func<T0, System.Object>"
+            },
+            {
+              "Name": "defaultFactory",
+              "Type": "System.Func<System.Object>"
+            },
+            {
+              "Name": "setter",
+              "Type": "System.Action<T0, System.Object>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "getter",
+              "Type": "System.Func<T0, System.Object>"
+            },
+            {
+              "Name": "defaultFactory",
+              "Type": "System.Func<System.Object>"
+            },
+            {
+              "Name": "setter",
+              "Type": "System.Action<T0, System.Object>"
+            },
+            {
+              "Name": "featureFactory",
+              "Type": "System.Func<T0>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": [
+        {
+          "ParameterName": "TFeature",
+          "ParameterPosition": 0,
+          "BaseTypeOrInterfaces": []
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/Http/Owin/test/Microsoft.AspNetCore.Owin.Tests.csproj b/src/Http/Owin/test/Microsoft.AspNetCore.Owin.Tests.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..359aff75b9914fdfac6d06f38dc5bc0b8a20d8bc
--- /dev/null
+++ b/src/Http/Owin/test/Microsoft.AspNetCore.Owin.Tests.csproj
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Http" />
+    <Reference Include="Microsoft.AspNetCore.Owin" />
+    <Reference Include="Microsoft.Extensions.DependencyInjection" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/Owin/test/OwinEnvironmentTests.cs b/src/Http/Owin/test/OwinEnvironmentTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b7288029143608d378e4436629f5d6ec068dce80
--- /dev/null
+++ b/src/Http/Owin/test/OwinEnvironmentTests.cs
@@ -0,0 +1,148 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Security.Claims;
+using System.Threading;
+using Microsoft.AspNetCore.Http;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Owin
+{
+    public class OwinEnvironmentTests
+    {
+        private T Get<T>(IDictionary<string, object> environment, string key)
+        {
+            object value;
+            return environment.TryGetValue(key, out value) ? (T)value : default(T);
+        }
+
+        [Fact]
+        public void OwinEnvironmentCanBeCreated()
+        {
+            HttpContext context = CreateContext();
+            context.Request.Method = "SomeMethod";
+            context.User = new ClaimsPrincipal(new ClaimsIdentity("Foo"));
+            context.Request.Body = Stream.Null;
+            context.Request.Headers["CustomRequestHeader"] = "CustomRequestValue";
+            context.Request.Path = new PathString("/path");
+            context.Request.PathBase = new PathString("/pathBase");
+            context.Request.Protocol = "http/1.0";
+            context.Request.QueryString = new QueryString("?key=value");
+            context.Request.Scheme = "http";
+            context.Response.Body = Stream.Null;
+            context.Response.Headers["CustomResponseHeader"] = "CustomResponseValue";
+            context.Response.StatusCode = 201;
+
+            IDictionary<string, object> env = new OwinEnvironment(context);
+            Assert.Equal("SomeMethod", Get<string>(env, "owin.RequestMethod"));
+            // User property should set both server.User (non-standard) and owin.RequestUser.
+            Assert.Equal("Foo", Get<ClaimsPrincipal>(env, "server.User").Identity.AuthenticationType);
+            Assert.Equal("Foo", Get<ClaimsPrincipal>(env, "owin.RequestUser").Identity.AuthenticationType);
+            Assert.Same(Stream.Null, Get<Stream>(env, "owin.RequestBody"));
+            var requestHeaders = Get<IDictionary<string, string[]>>(env, "owin.RequestHeaders");
+            Assert.NotNull(requestHeaders);
+            Assert.Equal("CustomRequestValue", requestHeaders["CustomRequestHeader"].First());
+            Assert.Equal("/path", Get<string>(env, "owin.RequestPath"));
+            Assert.Equal("/pathBase", Get<string>(env, "owin.RequestPathBase"));
+            Assert.Equal("http/1.0", Get<string>(env, "owin.RequestProtocol"));
+            Assert.Equal("key=value", Get<string>(env, "owin.RequestQueryString"));
+            Assert.Equal("http", Get<string>(env, "owin.RequestScheme"));
+
+            Assert.Same(Stream.Null, Get<Stream>(env, "owin.ResponseBody"));
+            var responseHeaders = Get<IDictionary<string, string[]>>(env, "owin.ResponseHeaders");
+            Assert.NotNull(responseHeaders);
+            Assert.Equal("CustomResponseValue", responseHeaders["CustomResponseHeader"].First());
+            Assert.Equal(201, Get<int>(env, "owin.ResponseStatusCode"));
+        }
+
+        [Fact]
+        public void OwinEnvironmentCanBeModified()
+        {
+            HttpContext context = CreateContext();
+            IDictionary<string, object> env = new OwinEnvironment(context);
+
+            env["owin.RequestMethod"] = "SomeMethod";
+            env["server.User"] = new ClaimsPrincipal(new ClaimsIdentity("Foo"));
+            Assert.Equal("Foo", context.User.Identity.AuthenticationType);
+            // User property should fall back from owin.RequestUser to server.User.
+            env["owin.RequestUser"] = new ClaimsPrincipal(new ClaimsIdentity("Bar"));
+            Assert.Equal("Bar", context.User.Identity.AuthenticationType);
+            env["owin.RequestBody"] = Stream.Null;
+            var requestHeaders = Get<IDictionary<string, string[]>>(env, "owin.RequestHeaders");
+            Assert.NotNull(requestHeaders);
+            requestHeaders["CustomRequestHeader"] = new[] { "CustomRequestValue" };
+            env["owin.RequestPath"] = "/path";
+            env["owin.RequestPathBase"] = "/pathBase";
+            env["owin.RequestProtocol"] = "http/1.0";
+            env["owin.RequestQueryString"] = "key=value";
+            env["owin.RequestScheme"] = "http";
+            env["owin.ResponseBody"] = Stream.Null;
+            var responseHeaders = Get<IDictionary<string, string[]>>(env, "owin.ResponseHeaders");
+            Assert.NotNull(responseHeaders);
+            responseHeaders["CustomResponseHeader"] = new[] { "CustomResponseValue" };
+            env["owin.ResponseStatusCode"] = 201;
+
+            Assert.Equal("SomeMethod", context.Request.Method);
+            Assert.Same(Stream.Null, context.Request.Body);
+            Assert.Equal("CustomRequestValue", context.Request.Headers["CustomRequestHeader"]);
+            Assert.Equal("/path", context.Request.Path.Value);
+            Assert.Equal("/pathBase", context.Request.PathBase.Value);
+            Assert.Equal("http/1.0", context.Request.Protocol);
+            Assert.Equal("?key=value", context.Request.QueryString.Value);
+            Assert.Equal("http", context.Request.Scheme);
+
+            Assert.Same(Stream.Null, context.Response.Body);
+            Assert.Equal("CustomResponseValue", context.Response.Headers["CustomResponseHeader"]);
+            Assert.Equal(201, context.Response.StatusCode);
+        }
+
+        [Theory]
+        [InlineData("server.LocalPort")]
+        public void OwinEnvironmentDoesNotContainEntriesForMissingFeatures(string key)
+        {
+            HttpContext context = CreateContext();
+            IDictionary<string, object> env = new OwinEnvironment(context);
+
+            object value;
+            Assert.False(env.TryGetValue(key, out value));
+
+            Assert.Throws<KeyNotFoundException>(() => env[key]);
+
+            Assert.False(env.Keys.Contains(key));
+            Assert.False(env.ContainsKey(key));
+        }
+
+        [Fact]
+        public void OwinEnvironmentSuppliesDefaultsForMissingRequiredEntries()
+        {
+            HttpContext context = CreateContext();
+            IDictionary<string, object> env = new OwinEnvironment(context);
+
+            object value;
+            Assert.True(env.TryGetValue("owin.CallCancelled", out value), "owin.CallCancelled");
+            Assert.True(env.TryGetValue("owin.Version", out value), "owin.Version");
+
+            Assert.Equal(CancellationToken.None, env["owin.CallCancelled"]);
+            Assert.Equal("1.0", env["owin.Version"]);
+        }
+
+        [Fact]
+        public void OwinEnvironmentImpelmentsGetEnumerator()
+        {
+            var owinEnvironment = new OwinEnvironment(CreateContext());
+
+            Assert.NotNull(owinEnvironment.GetEnumerator());
+            Assert.NotNull(((IEnumerable)owinEnvironment).GetEnumerator());
+        }
+
+        private HttpContext CreateContext()
+        {
+            var context = new DefaultHttpContext();
+            return context;
+        }
+    }
+}
diff --git a/src/Http/Owin/test/OwinExtensionTests.cs b/src/Http/Owin/test/OwinExtensionTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c4c51fba0a62fc70e3e0e325c2ed7e6cd148259e
--- /dev/null
+++ b/src/Http/Owin/test/OwinExtensionTests.cs
@@ -0,0 +1,164 @@
+// 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.Builder;
+using Microsoft.AspNetCore.Builder.Internal;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Owin
+{
+    using AddMiddleware = Action<Func<
+          Func<IDictionary<string, object>, Task>,
+          Func<IDictionary<string, object>, Task>
+        >>;
+    using AppFunc = Func<IDictionary<string, object>, Task>;
+    using CreateMiddleware = Func<
+          Func<IDictionary<string, object>, Task>,
+          Func<IDictionary<string, object>, Task>
+        >;
+
+    public class OwinExtensionTests
+    {
+        static AppFunc notFound = env => new Task(() => { env["owin.ResponseStatusCode"] = 404; });
+
+        [Fact]
+        public async Task OwinConfigureServiceProviderAddsServices()
+        {
+            var list = new List<CreateMiddleware>();
+            AddMiddleware build = list.Add;
+            IServiceProvider serviceProvider = null;
+            FakeService fakeService = null;
+
+            var builder = build.UseBuilder(applicationBuilder =>
+            {
+                serviceProvider = applicationBuilder.ApplicationServices;
+                applicationBuilder.Run(context =>
+                {
+                    fakeService = context.RequestServices.GetService<FakeService>();
+                    return Task.FromResult(0);
+                });
+            },
+            new ServiceCollection().AddSingleton(new FakeService()).BuildServiceProvider());
+
+            list.Reverse();
+            await list
+                .Aggregate(notFound, (next, middleware) => middleware(next))
+                .Invoke(new Dictionary<string, object>());
+
+            Assert.NotNull(serviceProvider);
+            Assert.NotNull(serviceProvider.GetService<FakeService>());
+            Assert.NotNull(fakeService);
+        }
+
+        [Fact]
+        public async Task OwinDefaultNoServices()
+        {
+            var list = new List<CreateMiddleware>();
+            AddMiddleware build = list.Add;
+            IServiceProvider expectedServiceProvider = new ServiceCollection().BuildServiceProvider();
+            IServiceProvider serviceProvider = null;
+            FakeService fakeService = null;
+            bool builderExecuted = false;
+            bool applicationExecuted = false;
+
+            var builder = build.UseBuilder(applicationBuilder =>
+            {
+                builderExecuted = true;
+                serviceProvider = applicationBuilder.ApplicationServices;
+                applicationBuilder.Run(context =>
+                {
+                    applicationExecuted = true;
+                    fakeService = context.RequestServices.GetService<FakeService>();
+                    return Task.FromResult(0);
+                });
+            },
+            expectedServiceProvider);
+
+            list.Reverse();
+            await list
+                .Aggregate(notFound, (next, middleware) => middleware(next))
+                .Invoke(new Dictionary<string, object>());
+
+            Assert.True(builderExecuted);
+            Assert.Equal(expectedServiceProvider, serviceProvider);
+            Assert.True(applicationExecuted);
+            Assert.Null(fakeService);
+        }
+
+        [Fact]
+        public async Task OwinDefaultNullServiceProvider()
+        {
+            var list = new List<CreateMiddleware>();
+            AddMiddleware build = list.Add;
+            IServiceProvider serviceProvider = null;
+            FakeService fakeService = null;
+            bool builderExecuted = false;
+            bool applicationExecuted = false;
+
+            var builder = build.UseBuilder(applicationBuilder =>
+            {
+                builderExecuted = true;
+                serviceProvider = applicationBuilder.ApplicationServices;
+                applicationBuilder.Run(context =>
+                {
+                    applicationExecuted = true;
+                    fakeService = context.RequestServices.GetService<FakeService>();
+                    return Task.FromResult(0);
+                });
+            });
+
+            list.Reverse();
+            await list
+                .Aggregate(notFound, (next, middleware) => middleware(next))
+                .Invoke(new Dictionary<string, object>());
+
+            Assert.True(builderExecuted);
+            Assert.NotNull(serviceProvider);
+            Assert.True(applicationExecuted);
+            Assert.Null(fakeService);
+        }
+
+        [Fact]
+        public async Task UseOwin()
+        {
+            var serviceProvider = new ServiceCollection().BuildServiceProvider();
+            var builder = new ApplicationBuilder(serviceProvider);
+            IDictionary<string, object> environment = null;
+            var context = new DefaultHttpContext();
+
+            builder.UseOwin(addToPipeline =>
+            {
+                addToPipeline(next =>
+                {
+                    Assert.NotNull(next);
+                    return async env =>
+                    {
+                        environment = env;
+                        await next(env);
+                    };
+                });
+            });
+            await builder.Build().Invoke(context);
+
+            // Dictionary contains context but does not contain "websocket.Accept" or "websocket.AcceptAlt" keys.
+            Assert.NotNull(environment);
+            var value = Assert.Single(
+                    environment,
+                    kvp => string.Equals(typeof(HttpContext).FullName, kvp.Key, StringComparison.Ordinal))
+                .Value;
+            Assert.Equal(context, value);
+            Assert.False(environment.ContainsKey("websocket.Accept"));
+            Assert.False(environment.ContainsKey("websocket.AcceptAlt"));
+        }
+
+        private class FakeService
+        {
+        }
+    }
+}
diff --git a/src/Http/Owin/test/OwinFeatureCollectionTests.cs b/src/Http/Owin/test/OwinFeatureCollectionTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b2755961c83e37ff5f01c0fd860a1db3befd2fcc
--- /dev/null
+++ b/src/Http/Owin/test/OwinFeatureCollectionTests.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.Collections.Generic;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Owin
+{
+    public class OwinHttpEnvironmentTests
+    {
+        private T Get<T>(IFeatureCollection features)
+        {
+            return (T)features[typeof(T)];
+        }
+
+        private T Get<T>(IDictionary<string, object> env, string key)
+        {
+            object value;
+            return env.TryGetValue(key, out value) ? (T)value : default(T);
+        }
+
+        [Fact]
+        public void OwinHttpEnvironmentCanBeCreated()
+        {
+            var env = new Dictionary<string, object>
+            {
+                { "owin.RequestMethod", HttpMethods.Post },
+                { "owin.RequestPath", "/path" },
+                { "owin.RequestPathBase", "/pathBase" },
+                { "owin.RequestQueryString", "name=value" },
+            };
+            var features = new OwinFeatureCollection(env);
+
+            var requestFeature = Get<IHttpRequestFeature>(features);
+            Assert.Equal(requestFeature.Method, HttpMethods.Post);
+            Assert.Equal("/path", requestFeature.Path);
+            Assert.Equal("/pathBase", requestFeature.PathBase);
+            Assert.Equal("?name=value", requestFeature.QueryString);
+        }
+
+        [Fact]
+        public void OwinHttpEnvironmentCanBeModified()
+        {
+            var env = new Dictionary<string, object>
+            {
+                { "owin.RequestMethod", HttpMethods.Post },
+                { "owin.RequestPath", "/path" },
+                { "owin.RequestPathBase", "/pathBase" },
+                { "owin.RequestQueryString", "name=value" },
+            };
+            var features = new OwinFeatureCollection(env);
+
+            var requestFeature = Get<IHttpRequestFeature>(features);
+            requestFeature.Method = HttpMethods.Get;
+            requestFeature.Path = "/path2";
+            requestFeature.PathBase = "/pathBase2";
+            requestFeature.QueryString = "?name=value2";
+
+            Assert.Equal(HttpMethods.Get, Get<string>(env, "owin.RequestMethod"));
+            Assert.Equal("/path2", Get<string>(env, "owin.RequestPath"));
+            Assert.Equal("/pathBase2", Get<string>(env, "owin.RequestPathBase"));
+            Assert.Equal("name=value2", Get<string>(env, "owin.RequestQueryString"));
+        }
+    }
+}
+
diff --git a/src/Http/README.md b/src/Http/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..58e2500a02c84db7c7c5f43645e86b09dab17a2e
--- /dev/null
+++ b/src/Http/README.md
@@ -0,0 +1,6 @@
+Http Abstractions
+=================
+
+This folders contains projects for HTTP abstractions for ASP.NET Core such as `HttpContext`, `HttpRequest`, `HttpResponse` and `RequestDelegate`.
+
+It also contains `IApplicationBuilder` and extensions to create and compose your application's pipeline.
diff --git a/src/Http/WebUtilities/src/Base64UrlTextEncoder.cs b/src/Http/WebUtilities/src/Base64UrlTextEncoder.cs
new file mode 100644
index 0000000000000000000000000000000000000000..304ee6522f813dc66372a14f580c4b8ca705a709
--- /dev/null
+++ b/src/Http/WebUtilities/src/Base64UrlTextEncoder.cs
@@ -0,0 +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.
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    public static class Base64UrlTextEncoder
+    {
+        /// <summary>
+        /// Encodes supplied data into Base64 and replaces any URL encodable characters into non-URL encodable
+        /// characters.
+        /// </summary>
+        /// <param name="data">Data to be encoded.</param>
+        /// <returns>Base64 encoded string modified with non-URL encodable characters</returns>
+        public static string Encode(byte[] data)
+        {
+            return WebEncoders.Base64UrlEncode(data);
+        }
+
+        /// <summary>
+        /// Decodes supplied string by replacing the non-URL encodable characters with URL encodable characters and
+        /// then decodes the Base64 string.
+        /// </summary>
+        /// <param name="text">The string to be decoded.</param>
+        /// <returns>The decoded data.</returns>
+        public static byte[] Decode(string text)
+        {
+            return WebEncoders.Base64UrlDecode(text);
+        }
+    }
+}
diff --git a/src/Http/WebUtilities/src/BufferedReadStream.cs b/src/Http/WebUtilities/src/BufferedReadStream.cs
new file mode 100644
index 0000000000000000000000000000000000000000..10f1465f3a2e2b71d9ef0631350af765d7094989
--- /dev/null
+++ b/src/Http/WebUtilities/src/BufferedReadStream.cs
@@ -0,0 +1,431 @@
+// 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.Buffers;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    /// <summary>
+    /// A Stream that wraps another stream and allows reading lines.
+    /// The data is buffered in memory.
+    /// </summary>
+    public class BufferedReadStream : Stream
+    {
+        private const byte CR = (byte)'\r';
+        private const byte LF = (byte)'\n';
+
+        private readonly Stream _inner;
+        private readonly byte[] _buffer;
+        private readonly ArrayPool<byte> _bytePool;
+        private int _bufferOffset = 0;
+        private int _bufferCount = 0;
+        private bool _disposed;
+
+        /// <summary>
+        /// Creates a new stream.
+        /// </summary>
+        /// <param name="inner">The stream to wrap.</param>
+        /// <param name="bufferSize">Size of buffer in bytes.</param>
+        public BufferedReadStream(Stream inner, int bufferSize)
+            : this(inner, bufferSize, ArrayPool<byte>.Shared)
+        {
+        }
+
+        /// <summary>
+        /// Creates a new stream.
+        /// </summary>
+        /// <param name="inner">The stream to wrap.</param>
+        /// <param name="bufferSize">Size of buffer in bytes.</param>
+        /// <param name="bytePool">ArrayPool for the buffer.</param>
+        public BufferedReadStream(Stream inner, int bufferSize, ArrayPool<byte> bytePool)
+        {
+            if (inner == null)
+            {
+                throw new ArgumentNullException(nameof(inner));
+            }
+
+            _inner = inner;
+            _bytePool = bytePool;
+            _buffer = bytePool.Rent(bufferSize);
+        }
+
+        /// <summary>
+        /// The currently buffered data.
+        /// </summary>
+        public ArraySegment<byte> BufferedData
+        {
+            get { return new ArraySegment<byte>(_buffer, _bufferOffset, _bufferCount); }
+        }
+
+        /// <inheritdoc/>
+        public override bool CanRead
+        {
+            get { return _inner.CanRead || _bufferCount > 0; }
+        }
+
+        /// <inheritdoc/>
+        public override bool CanSeek
+        {
+            get { return _inner.CanSeek; }
+        }
+
+        /// <inheritdoc/>
+        public override bool CanTimeout
+        {
+            get { return _inner.CanTimeout; }
+        }
+
+        /// <inheritdoc/>
+        public override bool CanWrite
+        {
+            get { return _inner.CanWrite; }
+        }
+
+        /// <inheritdoc/>
+        public override long Length
+        {
+            get { return _inner.Length; }
+        }
+
+        /// <inheritdoc/>
+        public override long Position
+        {
+            get { return _inner.Position - _bufferCount; }
+            set
+            {
+                if (value < 0)
+                {
+                    throw new ArgumentOutOfRangeException(nameof(value), value, "Position must be positive.");
+                }
+                if (value == Position)
+                {
+                    return;
+                }
+
+                // Backwards?
+                if (value <= _inner.Position)
+                {
+                    // Forward within the buffer?
+                    var innerOffset = (int)(_inner.Position - value);
+                    if (innerOffset <= _bufferCount)
+                    {
+                        // Yes, just skip some of the buffered data
+                        _bufferOffset += innerOffset;
+                        _bufferCount -= innerOffset;
+                    }
+                    else
+                    {
+                        // No, reset the buffer
+                        _bufferOffset = 0;
+                        _bufferCount = 0;
+                        _inner.Position = value;
+                    }
+                }
+                else
+                {
+                    // Forward, reset the buffer
+                    _bufferOffset = 0;
+                    _bufferCount = 0;
+                    _inner.Position = value;
+                }
+            }
+        }
+
+        /// <inheritdoc/>
+        public override long Seek(long offset, SeekOrigin origin)
+        {
+            if (origin == SeekOrigin.Begin)
+            {
+                Position = offset;
+            }
+            else if (origin == SeekOrigin.Current)
+            {
+                Position = Position + offset;
+            }
+            else // if (origin == SeekOrigin.End)
+            {
+                Position = Length + offset;
+            }
+            return Position;
+        }
+
+        /// <inheritdoc/>
+        public override void SetLength(long value)
+        {
+            _inner.SetLength(value);
+        }
+
+        /// <inheritdoc/>
+        protected override void Dispose(bool disposing)
+        {
+            if (!_disposed)
+            {
+                _disposed = true;
+                _bytePool.Return(_buffer);
+
+                if (disposing)
+                {
+                    _inner.Dispose();
+                }
+            }
+        }
+
+        /// <inheritdoc/>
+        public override void Flush()
+        {
+            _inner.Flush();
+        }
+
+        /// <inheritdoc/>
+        public override Task FlushAsync(CancellationToken cancellationToken)
+        {
+            return _inner.FlushAsync(cancellationToken);
+        }
+
+        /// <inheritdoc/>
+        public override void Write(byte[] buffer, int offset, int count)
+        {
+            _inner.Write(buffer, offset, count);
+        }
+
+        /// <inheritdoc/>
+        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            return _inner.WriteAsync(buffer, offset, count, cancellationToken);
+        }
+
+        /// <inheritdoc/>
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            ValidateBuffer(buffer, offset, count);
+
+            // Drain buffer
+            if (_bufferCount > 0)
+            {
+                int toCopy = Math.Min(_bufferCount, count);
+                Buffer.BlockCopy(_buffer, _bufferOffset, buffer, offset, toCopy);
+                _bufferOffset += toCopy;
+                _bufferCount -= toCopy;
+                return toCopy;
+            }
+
+            return _inner.Read(buffer, offset, count);
+        }
+
+        /// <inheritdoc/>
+        public async override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            ValidateBuffer(buffer, offset, count);
+
+            // Drain buffer
+            if (_bufferCount > 0)
+            {
+                int toCopy = Math.Min(_bufferCount, count);
+                Buffer.BlockCopy(_buffer, _bufferOffset, buffer, offset, toCopy);
+                _bufferOffset += toCopy;
+                _bufferCount -= toCopy;
+                return toCopy;
+            }
+
+            return await _inner.ReadAsync(buffer, offset, count, cancellationToken);
+        }
+
+        /// <summary>
+        /// Ensures that the buffer is not empty.
+        /// </summary>
+        /// <returns>Returns <c>true</c> if the buffer is not empty; <c>false</c> otherwise.</returns>
+        public bool EnsureBuffered()
+        {
+            if (_bufferCount > 0)
+            {
+                return true;
+            }
+            // Downshift to make room
+            _bufferOffset = 0;
+            _bufferCount = _inner.Read(_buffer, 0, _buffer.Length);
+            return _bufferCount > 0;
+        }
+
+        /// <summary>
+        /// Ensures that the buffer is not empty.
+        /// </summary>
+        /// <param name="cancellationToken">Cancellation token.</param>
+        /// <returns>Returns <c>true</c> if the buffer is not empty; <c>false</c> otherwise.</returns>
+        public async Task<bool> EnsureBufferedAsync(CancellationToken cancellationToken)
+        {
+            if (_bufferCount > 0)
+            {
+                return true;
+            }
+            // Downshift to make room
+            _bufferOffset = 0;
+            _bufferCount = await _inner.ReadAsync(_buffer, 0, _buffer.Length, cancellationToken);
+            return _bufferCount > 0;
+        }
+
+        /// <summary>
+        /// Ensures that a minimum amount of buffered data is available.
+        /// </summary>
+        /// <param name="minCount">Minimum amount of buffered data.</param>
+        /// <returns>Returns <c>true</c> if the minimum amount of buffered data is available; <c>false</c> otherwise.</returns>
+        public bool EnsureBuffered(int minCount)
+        {
+            if (minCount > _buffer.Length)
+            {
+                throw new ArgumentOutOfRangeException(nameof(minCount), minCount, "The value must be smaller than the buffer size: " + _buffer.Length.ToString());
+            }
+            while (_bufferCount < minCount)
+            {
+                // Downshift to make room
+                if (_bufferOffset > 0)
+                {
+                    if (_bufferCount > 0)
+                    {
+                        Buffer.BlockCopy(_buffer, _bufferOffset, _buffer, 0, _bufferCount);
+                    }
+                    _bufferOffset = 0;
+                }
+                int read = _inner.Read(_buffer, _bufferOffset + _bufferCount, _buffer.Length - _bufferCount - _bufferOffset);
+                _bufferCount += read;
+                if (read == 0)
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// Ensures that a minimum amount of buffered data is available.
+        /// </summary>
+        /// <param name="minCount">Minimum amount of buffered data.</param>
+        /// <param name="cancellationToken">Cancellation token.</param>
+        /// <returns>Returns <c>true</c> if the minimum amount of buffered data is available; <c>false</c> otherwise.</returns>
+        public async Task<bool> EnsureBufferedAsync(int minCount, CancellationToken cancellationToken)
+        {
+            if (minCount > _buffer.Length)
+            {
+                throw new ArgumentOutOfRangeException(nameof(minCount), minCount, "The value must be smaller than the buffer size: " + _buffer.Length.ToString());
+            }
+            while (_bufferCount < minCount)
+            {
+                // Downshift to make room
+                if (_bufferOffset > 0)
+                {
+                    if (_bufferCount > 0)
+                    {
+                        Buffer.BlockCopy(_buffer, _bufferOffset, _buffer, 0, _bufferCount);
+                    }
+                    _bufferOffset = 0;
+                }
+                int read = await _inner.ReadAsync(_buffer, _bufferOffset + _bufferCount, _buffer.Length - _bufferCount - _bufferOffset, cancellationToken);
+                _bufferCount += read;
+                if (read == 0)
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// Reads a line. A line is defined as a sequence of characters followed by
+        /// a carriage return immediately followed by a line feed. The resulting string does not
+        /// contain the terminating carriage return and line feed.
+        /// </summary>
+        /// <param name="lengthLimit">Maximum allowed line length.</param>
+        /// <returns>A line.</returns>
+        public string ReadLine(int lengthLimit)
+        {
+            CheckDisposed();
+            using (var builder = new MemoryStream(200))
+            {
+                bool foundCR = false, foundCRLF = false;
+
+                while (!foundCRLF && EnsureBuffered())
+                {
+                    if (builder.Length > lengthLimit)
+                    {
+                        throw new InvalidDataException($"Line length limit {lengthLimit} exceeded.");
+                    }
+                    ProcessLineChar(builder, ref foundCR, ref foundCRLF);
+                }
+
+                return DecodeLine(builder, foundCRLF);
+            }
+        }
+
+        /// <summary>
+        /// Reads a line. A line is defined as a sequence of characters followed by
+        /// a carriage return immediately followed by a line feed. The resulting string does not
+        /// contain the terminating carriage return and line feed.
+        /// </summary>
+        /// <param name="lengthLimit">Maximum allowed line length.</param>
+        /// <param name="cancellationToken">Cancellation token.</param>
+        /// <returns>A line.</returns>
+        public async Task<string> ReadLineAsync(int lengthLimit, CancellationToken cancellationToken)
+        {
+            CheckDisposed();
+            using (var builder = new MemoryStream(200))
+            {
+                bool foundCR = false, foundCRLF = false;
+
+                while (!foundCRLF && await EnsureBufferedAsync(cancellationToken))
+                {
+                    if (builder.Length > lengthLimit)
+                    {
+                        throw new InvalidDataException($"Line length limit {lengthLimit} exceeded.");
+                    }
+
+                    ProcessLineChar(builder, ref foundCR, ref foundCRLF);
+                }
+
+                return DecodeLine(builder, foundCRLF);
+            }
+        }
+
+        private void ProcessLineChar(MemoryStream builder, ref bool foundCR, ref bool foundCRLF)
+        {
+            var b = _buffer[_bufferOffset];
+            builder.WriteByte(b);
+            _bufferOffset++;
+            _bufferCount--;
+            if (b == LF && foundCR)
+            {
+                foundCRLF = true;
+                return;
+            }
+            foundCR = b == CR;
+        }
+
+        private string DecodeLine(MemoryStream builder, bool foundCRLF)
+        {
+            // Drop the final CRLF, if any
+            var length = foundCRLF ? builder.Length - 2 : builder.Length;
+            return Encoding.UTF8.GetString(builder.ToArray(), 0, (int)length);
+        }
+
+        private void CheckDisposed()
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(BufferedReadStream));
+            }
+        }
+
+        private void ValidateBuffer(byte[] buffer, int offset, int count)
+        {
+            // Delegate most of our validation.
+            var ignored = new ArraySegment<byte>(buffer, offset, count);
+            if (count == 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(count), "The value must be greater than zero.");
+            }
+        }
+    }
+}
diff --git a/src/Http/WebUtilities/src/FileBufferingReadStream.cs b/src/Http/WebUtilities/src/FileBufferingReadStream.cs
new file mode 100644
index 0000000000000000000000000000000000000000..9dd1fbf13f3c3821dc612d32a4628952b4e8479c
--- /dev/null
+++ b/src/Http/WebUtilities/src/FileBufferingReadStream.cs
@@ -0,0 +1,354 @@
+// 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.Buffers;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    /// <summary>
+    /// A Stream that wraps another stream and enables rewinding by buffering the content as it is read.
+    /// The content is buffered in memory up to a certain size and then spooled to a temp file on disk.
+    /// The temp file will be deleted on Dispose.
+    /// </summary>
+    public class FileBufferingReadStream : Stream
+    {
+        private const int _maxRentedBufferSize = 1024 * 1024; // 1MB
+        private readonly Stream _inner;
+        private readonly ArrayPool<byte> _bytePool;
+        private readonly int _memoryThreshold;
+        private readonly long? _bufferLimit;
+        private string _tempFileDirectory;
+        private readonly Func<string> _tempFileDirectoryAccessor;
+        private string _tempFileName;
+
+        private Stream _buffer;
+        private byte[] _rentedBuffer;
+        private bool _inMemory = true;
+        private bool _completelyBuffered;
+
+        private bool _disposed;
+
+        public FileBufferingReadStream(
+            Stream inner,
+            int memoryThreshold,
+            long? bufferLimit,
+            Func<string> tempFileDirectoryAccessor)
+            : this(inner, memoryThreshold, bufferLimit, tempFileDirectoryAccessor, ArrayPool<byte>.Shared)
+        {
+        }
+
+        public FileBufferingReadStream(
+            Stream inner,
+            int memoryThreshold,
+            long? bufferLimit,
+            Func<string> tempFileDirectoryAccessor,
+            ArrayPool<byte> bytePool)
+        {
+            if (inner == null)
+            {
+                throw new ArgumentNullException(nameof(inner));
+            }
+
+            if (tempFileDirectoryAccessor == null)
+            {
+                throw new ArgumentNullException(nameof(tempFileDirectoryAccessor));
+            }
+
+            _bytePool = bytePool;
+            if (memoryThreshold < _maxRentedBufferSize)
+            {
+                _rentedBuffer = bytePool.Rent(memoryThreshold);
+                _buffer = new MemoryStream(_rentedBuffer);
+                _buffer.SetLength(0);
+            }
+            else
+            {
+                _buffer = new MemoryStream();
+            }
+
+            _inner = inner;
+            _memoryThreshold = memoryThreshold;
+            _bufferLimit = bufferLimit;
+            _tempFileDirectoryAccessor = tempFileDirectoryAccessor;
+        }
+
+        public FileBufferingReadStream(
+            Stream inner,
+            int memoryThreshold,
+            long? bufferLimit,
+            string tempFileDirectory)
+            : this(inner, memoryThreshold, bufferLimit, tempFileDirectory, ArrayPool<byte>.Shared)
+        {
+        }
+
+        public FileBufferingReadStream(
+            Stream inner,
+            int memoryThreshold,
+            long? bufferLimit,
+            string tempFileDirectory,
+            ArrayPool<byte> bytePool)
+        {
+            if (inner == null)
+            {
+                throw new ArgumentNullException(nameof(inner));
+            }
+
+            if (tempFileDirectory == null)
+            {
+                throw new ArgumentNullException(nameof(tempFileDirectory));
+            }
+
+            _bytePool = bytePool;
+            if (memoryThreshold < _maxRentedBufferSize)
+            {
+                _rentedBuffer = bytePool.Rent(memoryThreshold);
+                _buffer = new MemoryStream(_rentedBuffer);
+                _buffer.SetLength(0);
+            }
+            else
+            {
+                _buffer = new MemoryStream();
+            }
+
+            _inner = inner;
+            _memoryThreshold = memoryThreshold;
+            _bufferLimit = bufferLimit;
+            _tempFileDirectory = tempFileDirectory;
+        }
+
+        public bool InMemory
+        {
+            get { return _inMemory; }
+        }
+
+        public string TempFileName
+        {
+            get { return _tempFileName; }
+        }
+
+        public override bool CanRead
+        {
+            get { return true; }
+        }
+
+        public override bool CanSeek
+        {
+            get { return true; }
+        }
+
+        public override bool CanWrite
+        {
+            get { return false; }
+        }
+
+        public override long Length
+        {
+            get { return _buffer.Length; }
+        }
+
+        public override long Position
+        {
+            get { return _buffer.Position; }
+            // Note this will not allow seeking forward beyond the end of the buffer.
+            set
+            {
+                ThrowIfDisposed();
+                _buffer.Position = value;
+            }
+        }
+
+        public override long Seek(long offset, SeekOrigin origin)
+        {
+            ThrowIfDisposed();
+            if (!_completelyBuffered && origin == SeekOrigin.End)
+            {
+                // Can't seek from the end until we've finished consuming the inner stream
+                throw new NotSupportedException("The content has not been fully buffered yet.");
+            }
+            else if (!_completelyBuffered && origin == SeekOrigin.Current && offset + Position > Length)
+            {
+                // Can't seek past the end of the buffer until we've finished consuming the inner stream
+                throw new NotSupportedException("The content has not been fully buffered yet.");
+            }
+            else if (!_completelyBuffered && origin == SeekOrigin.Begin && offset > Length)
+            {
+                // Can't seek past the end of the buffer until we've finished consuming the inner stream
+                throw new NotSupportedException("The content has not been fully buffered yet.");
+            }
+            return _buffer.Seek(offset, origin);
+        }
+
+        private Stream CreateTempFile()
+        {
+            if (_tempFileDirectory == null)
+            {
+                Debug.Assert(_tempFileDirectoryAccessor != null);
+                _tempFileDirectory = _tempFileDirectoryAccessor();
+                Debug.Assert(_tempFileDirectory != null);
+            }
+
+            _tempFileName = Path.Combine(_tempFileDirectory, "ASPNETCORE_" + Guid.NewGuid().ToString() + ".tmp");
+            return new FileStream(_tempFileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Delete, 1024 * 16,
+                FileOptions.Asynchronous | FileOptions.DeleteOnClose | FileOptions.SequentialScan);
+        }
+
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            ThrowIfDisposed();
+            if (_buffer.Position < _buffer.Length || _completelyBuffered)
+            {
+                // Just read from the buffer
+                return _buffer.Read(buffer, offset, (int)Math.Min(count, _buffer.Length - _buffer.Position));
+            }
+
+            int read = _inner.Read(buffer, offset, count);
+
+            if (_bufferLimit.HasValue && _bufferLimit - read < _buffer.Length)
+            {
+                Dispose();
+                throw new IOException("Buffer limit exceeded.");
+            }
+
+            if (_inMemory && _buffer.Length + read > _memoryThreshold)
+            {
+                _inMemory = false;
+                var oldBuffer = _buffer;
+                _buffer = CreateTempFile();
+                if (_rentedBuffer == null)
+                {
+                    oldBuffer.Position = 0;
+                    var rentedBuffer = _bytePool.Rent(Math.Min((int)oldBuffer.Length, _maxRentedBufferSize));
+                    var copyRead = oldBuffer.Read(rentedBuffer, 0, rentedBuffer.Length);
+                    while (copyRead > 0)
+                    {
+                        _buffer.Write(rentedBuffer, 0, copyRead);
+                        copyRead = oldBuffer.Read(rentedBuffer, 0, rentedBuffer.Length);
+                    }
+                    _bytePool.Return(rentedBuffer);
+                }
+                else
+                {
+                    _buffer.Write(_rentedBuffer, 0, (int)oldBuffer.Length);
+                    _bytePool.Return(_rentedBuffer);
+                    _rentedBuffer = null;
+                }
+            }
+
+            if (read > 0)
+            {
+                _buffer.Write(buffer, offset, read);
+            }
+            else
+            {
+                _completelyBuffered = true;
+            }
+
+            return read;
+        }
+
+        public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            ThrowIfDisposed();
+            if (_buffer.Position < _buffer.Length || _completelyBuffered)
+            {
+                // Just read from the buffer
+                return await _buffer.ReadAsync(buffer, offset, (int)Math.Min(count, _buffer.Length - _buffer.Position), cancellationToken);
+            }
+
+            int read = await _inner.ReadAsync(buffer, offset, count, cancellationToken);
+
+            if (_bufferLimit.HasValue && _bufferLimit - read < _buffer.Length)
+            {
+                Dispose();
+                throw new IOException("Buffer limit exceeded.");
+            }
+
+            if (_inMemory && _buffer.Length + read > _memoryThreshold)
+            {
+                _inMemory = false;
+                var oldBuffer = _buffer;
+                _buffer = CreateTempFile();
+                if (_rentedBuffer == null)
+                {
+                    oldBuffer.Position = 0;
+                    var rentedBuffer = _bytePool.Rent(Math.Min((int)oldBuffer.Length, _maxRentedBufferSize));
+                    // oldBuffer is a MemoryStream, no need to do async reads.
+                    var copyRead = oldBuffer.Read(rentedBuffer, 0, rentedBuffer.Length);
+                    while (copyRead > 0)
+                    {
+                        await _buffer.WriteAsync(rentedBuffer, 0, copyRead, cancellationToken);
+                        copyRead = oldBuffer.Read(rentedBuffer, 0, rentedBuffer.Length);
+                    }
+                    _bytePool.Return(rentedBuffer);
+                }
+                else
+                {
+                    await _buffer.WriteAsync(_rentedBuffer, 0, (int)oldBuffer.Length, cancellationToken);
+                    _bytePool.Return(_rentedBuffer);
+                    _rentedBuffer = null;
+                }
+            }
+
+            if (read > 0)
+            {
+                await _buffer.WriteAsync(buffer, offset, read, cancellationToken);
+            }
+            else
+            {
+                _completelyBuffered = true;
+            }
+
+            return read;
+        }
+
+        public override void Write(byte[] buffer, int offset, int count)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override void SetLength(long value)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override void Flush()
+        {
+            throw new NotSupportedException();
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (!_disposed)
+            {
+                _disposed = true;
+                if (_rentedBuffer != null)
+                {
+                    _bytePool.Return(_rentedBuffer);
+                }
+
+                if (disposing)
+                {
+                    _buffer.Dispose();
+                }
+            }
+        }
+
+        private void ThrowIfDisposed()
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(FileBufferingReadStream));
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/WebUtilities/src/FileMultipartSection.cs b/src/Http/WebUtilities/src/FileMultipartSection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..70d7741f64d5624d8015112810a7fe5738dcc643
--- /dev/null
+++ b/src/Http/WebUtilities/src/FileMultipartSection.cs
@@ -0,0 +1,70 @@
+// 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 Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    /// <summary>
+    /// Represents a file multipart section
+    /// </summary>
+    public class FileMultipartSection
+    {
+        private ContentDispositionHeaderValue _contentDispositionHeader;
+
+        /// <summary>
+        /// Creates a new instance of the <see cref="FileMultipartSection"/> class
+        /// </summary>
+        /// <param name="section">The section from which to create the <see cref="FileMultipartSection"/></param>
+        /// <remarks>Reparses the content disposition header</remarks>
+        public FileMultipartSection(MultipartSection section)
+            :this(section, section.GetContentDispositionHeader())
+        {
+        }
+
+        /// <summary>
+        /// Creates a new instance of the <see cref="FileMultipartSection"/> class
+        /// </summary>
+        /// <param name="section">The section from which to create the <see cref="FileMultipartSection"/></param>
+        /// <param name="header">An already parsed content disposition header</param>
+        public FileMultipartSection(MultipartSection section, ContentDispositionHeaderValue header)
+        {
+            if (!header.IsFileDisposition())
+            {
+                throw new ArgumentException($"Argument must be a file section", nameof(section));
+            }
+
+            Section = section;
+            _contentDispositionHeader = header;
+
+            Name = HeaderUtilities.RemoveQuotes(_contentDispositionHeader.Name).ToString();
+            FileName = HeaderUtilities.RemoveQuotes(
+                    _contentDispositionHeader.FileNameStar.HasValue ?
+                        _contentDispositionHeader.FileNameStar :
+                        _contentDispositionHeader.FileName).ToString();
+        }
+
+        /// <summary>
+        /// Gets the original section from which this object was created
+        /// </summary>
+        public MultipartSection Section { get; }
+
+        /// <summary>
+        /// Gets the file stream from the section body
+        /// </summary>
+        public Stream FileStream => Section.Body;
+
+        /// <summary>
+        /// Gets the name of the section
+        /// </summary>
+        public string Name { get; }
+
+        /// <summary>
+        /// Gets the name of the file from the section
+        /// </summary>
+        public string FileName { get; }
+
+    }
+}
diff --git a/src/Http/WebUtilities/src/FormMultipartSection.cs b/src/Http/WebUtilities/src/FormMultipartSection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..01af0455b8809283875846b288aa2b4cecc1fd69
--- /dev/null
+++ b/src/Http/WebUtilities/src/FormMultipartSection.cs
@@ -0,0 +1,63 @@
+// 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;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    /// <summary>
+    /// Represents a form multipart section
+    /// </summary>
+    public class FormMultipartSection
+    {
+        private ContentDispositionHeaderValue _contentDispositionHeader;
+
+        /// <summary>
+        /// Creates a new instance of the <see cref="FormMultipartSection"/> class
+        /// </summary>
+        /// <param name="section">The section from which to create the <see cref="FormMultipartSection"/></param>
+        /// <remarks>Reparses the content disposition header</remarks>
+        public FormMultipartSection(MultipartSection section)
+            : this(section, section.GetContentDispositionHeader())
+        {
+        }
+
+        /// <summary>
+        /// Creates a new instance of the <see cref="FormMultipartSection"/> class
+        /// </summary>
+        /// <param name="section">The section from which to create the <see cref="FormMultipartSection"/></param>
+        /// <param name="header">An already parsed content disposition header</param>
+        public FormMultipartSection(MultipartSection section, ContentDispositionHeaderValue header)
+        {
+            if (header == null || !header.IsFormDisposition())
+            {
+                throw new ArgumentException($"Argument must be a form section", nameof(section));
+            }
+
+            Section = section;
+            _contentDispositionHeader = header;
+            Name = HeaderUtilities.RemoveQuotes(_contentDispositionHeader.Name).ToString();
+        }
+
+        /// <summary>
+        /// Gets the original section from which this object was created
+        /// </summary>
+        public MultipartSection Section { get; }
+
+        /// <summary>
+        /// The form name
+        /// </summary>
+        public string Name { get; }
+
+        /// <summary>
+        /// Gets the form value
+        /// </summary>
+        /// <returns>The form value</returns>
+        public Task<string> GetValueAsync()
+        {
+            return Section.ReadAsStringAsync();
+        }
+    }
+}
diff --git a/src/Http/WebUtilities/src/FormReader.cs b/src/Http/WebUtilities/src/FormReader.cs
new file mode 100644
index 0000000000000000000000000000000000000000..958a4971fab7e4651379129fc48fab6d135d1b8f
--- /dev/null
+++ b/src/Http/WebUtilities/src/FormReader.cs
@@ -0,0 +1,312 @@
+// 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.Buffers;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    /// <summary>
+    /// Used to read an 'application/x-www-form-urlencoded' form.
+    /// </summary>
+    public class FormReader : IDisposable
+    {
+        public const int DefaultValueCountLimit = 1024;
+        public const int DefaultKeyLengthLimit = 1024 * 2;
+        public const int DefaultValueLengthLimit = 1024 * 1024 * 4;
+
+        private const int _rentedCharPoolLength = 8192;
+        private readonly TextReader _reader;
+        private readonly char[] _buffer;
+        private readonly ArrayPool<char> _charPool;
+        private readonly StringBuilder _builder = new StringBuilder();
+        private int _bufferOffset;
+        private int _bufferCount;
+        private string _currentKey;
+        private string _currentValue;
+        private bool _endOfStream;
+        private bool _disposed;
+
+        public FormReader(string data)
+            : this(data, ArrayPool<char>.Shared)
+        {
+        }
+
+        public FormReader(string data, ArrayPool<char> charPool)
+        {
+            if (data == null)
+            {
+                throw new ArgumentNullException(nameof(data));
+            }
+
+            _buffer = charPool.Rent(_rentedCharPoolLength);
+            _charPool = charPool;
+            _reader = new StringReader(data);
+        }
+
+        public FormReader(Stream stream)
+            : this(stream, Encoding.UTF8, ArrayPool<char>.Shared)
+        {
+        }
+
+        public FormReader(Stream stream, Encoding encoding)
+            : this(stream, encoding, ArrayPool<char>.Shared)
+        {
+        }
+
+        public FormReader(Stream stream, Encoding encoding, ArrayPool<char> charPool)
+        {
+            if (stream == null)
+            {
+                throw new ArgumentNullException(nameof(stream));
+            }
+
+            if (encoding == null)
+            {
+                throw new ArgumentNullException(nameof(encoding));
+            }
+
+            _buffer = charPool.Rent(_rentedCharPoolLength);
+            _charPool = charPool;
+            _reader = new StreamReader(stream, encoding, detectEncodingFromByteOrderMarks: true, bufferSize: 1024 * 2, leaveOpen: true);
+        }
+
+        /// <summary>
+        /// The limit on the number of form values to allow in ReadForm or ReadFormAsync.
+        /// </summary>
+        public int ValueCountLimit { get; set; } = DefaultValueCountLimit;
+
+        /// <summary>
+        /// The limit on the length of form keys.
+        /// </summary>
+        public int KeyLengthLimit { get; set; } = DefaultKeyLengthLimit;
+
+        /// <summary>
+        /// The limit on the length of form values.
+        /// </summary>
+        public int ValueLengthLimit { get; set; } = DefaultValueLengthLimit;
+
+        // Format: key1=value1&key2=value2
+        /// <summary>
+        /// Reads the next key value pair from the form.
+        /// For unbuffered data use the async overload instead.
+        /// </summary>
+        /// <returns>The next key value pair, or null when the end of the form is reached.</returns>
+        public KeyValuePair<string, string>? ReadNextPair()
+        {
+            ReadNextPairImpl();
+            if (ReadSucceded())
+            {
+                return new KeyValuePair<string, string>(_currentKey, _currentValue);
+            }
+            return null;
+        }
+
+        private void ReadNextPairImpl()
+        {
+            StartReadNextPair();
+            while (!_endOfStream)
+            {
+                // Empty
+                if (_bufferCount == 0)
+                {
+                    Buffer();
+                }
+                if (TryReadNextPair())
+                {
+                    break;
+                }
+            }
+        }
+
+        // Format: key1=value1&key2=value2
+        /// <summary>
+        /// Asynchronously reads the next key value pair from the form.
+        /// </summary>
+        /// <param name="cancellationToken"></param>
+        /// <returns>The next key value pair, or null when the end of the form is reached.</returns>
+        public async Task<KeyValuePair<string, string>?> ReadNextPairAsync(CancellationToken cancellationToken = new CancellationToken())
+        {
+            await ReadNextPairAsyncImpl(cancellationToken);
+            if (ReadSucceded())
+            {
+                return new KeyValuePair<string, string>(_currentKey, _currentValue);
+            }
+            return null;
+        }
+
+        private async Task ReadNextPairAsyncImpl(CancellationToken cancellationToken = new CancellationToken())
+        {
+            StartReadNextPair();
+            while (!_endOfStream)
+            {
+                // Empty
+                if (_bufferCount == 0)
+                {
+                    await BufferAsync(cancellationToken);
+                }
+                if (TryReadNextPair())
+                {
+                    break;
+                }
+            }
+        }
+
+        private void StartReadNextPair()
+        {
+            _currentKey = null;
+            _currentValue = null;
+        }
+
+        private bool TryReadNextPair()
+        {
+            if (_currentKey == null)
+            {
+                if (!TryReadWord('=', KeyLengthLimit, out _currentKey))
+                {
+                    return false;
+                }
+
+                if (_bufferCount == 0)
+                {
+                    return false;
+                }
+            }
+
+            if (_currentValue == null)
+            {
+                if (!TryReadWord('&', ValueLengthLimit, out _currentValue))
+                {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private bool TryReadWord(char seperator, int limit, out string value)
+        {
+            do
+            {
+                if (ReadChar(seperator, limit, out value))
+                {
+                    return true;
+                }
+            } while (_bufferCount > 0);
+            return false;
+        }
+
+        private bool ReadChar(char seperator, int limit, out string word)
+        {
+            // End
+            if (_bufferCount == 0)
+            {
+                word = BuildWord();
+                return true;
+            }
+
+            var c = _buffer[_bufferOffset++];
+            _bufferCount--;
+
+            if (c == seperator)
+            {
+                word = BuildWord();
+                return true;
+            }
+            if (_builder.Length >= limit)
+            {
+                throw new InvalidDataException($"Form key or value length limit {limit} exceeded.");
+            }
+            _builder.Append(c);
+            word = null;
+            return false;
+        }
+
+        // '+' un-escapes to ' ', %HH un-escapes as ASCII (or utf-8?)
+        private string BuildWord()
+        {
+            _builder.Replace('+', ' ');
+            var result = _builder.ToString();
+            _builder.Clear();
+            return Uri.UnescapeDataString(result); // TODO: Replace this, it's not completely accurate.
+        }
+
+        private void Buffer()
+        {
+            _bufferOffset = 0;
+            _bufferCount = _reader.Read(_buffer, 0, _buffer.Length);
+            _endOfStream = _bufferCount == 0;
+        }
+
+        private async Task BufferAsync(CancellationToken cancellationToken)
+        {
+            // TODO: StreamReader doesn't support cancellation?
+            cancellationToken.ThrowIfCancellationRequested();
+            _bufferOffset = 0;
+            _bufferCount = await _reader.ReadAsync(_buffer, 0, _buffer.Length);
+            _endOfStream = _bufferCount == 0;
+        }
+
+        /// <summary>
+        /// Parses text from an HTTP form body.
+        /// </summary>
+        /// <returns>The collection containing the parsed HTTP form body.</returns>
+        public Dictionary<string, StringValues> ReadForm()
+        {
+            var accumulator = new KeyValueAccumulator();
+            while (!_endOfStream)
+            {
+                ReadNextPairImpl();
+                Append(ref accumulator);
+            }
+            return accumulator.GetResults();
+        }
+
+        /// <summary>
+        /// Parses an HTTP form body.
+        /// </summary>
+        /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
+        /// <returns>The collection containing the parsed HTTP form body.</returns>
+        public async Task<Dictionary<string, StringValues>> ReadFormAsync(CancellationToken cancellationToken = new CancellationToken())
+        {
+            var accumulator = new KeyValueAccumulator();
+            while (!_endOfStream)
+            {
+                await ReadNextPairAsyncImpl(cancellationToken);
+                Append(ref accumulator);
+            }
+            return accumulator.GetResults();
+        }
+
+        private bool ReadSucceded()
+        {
+            return _currentKey != null && _currentValue != null;
+        }
+
+        private void Append(ref KeyValueAccumulator accumulator)
+        {
+            if (ReadSucceded())
+            {
+                accumulator.Append(_currentKey, _currentValue);
+                if (accumulator.ValueCount > ValueCountLimit)
+                {
+                    throw new InvalidDataException($"Form value count limit {ValueCountLimit} exceeded.");
+                }
+            }
+        }
+
+        public void Dispose()
+        {
+            if (!_disposed)
+            {
+                _disposed = true;
+                _charPool.Return(_buffer);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/WebUtilities/src/HttpRequestStreamReader.cs b/src/Http/WebUtilities/src/HttpRequestStreamReader.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3f9478c5deaab88a443c7b3376b17cc4ebee9671
--- /dev/null
+++ b/src/Http/WebUtilities/src/HttpRequestStreamReader.cs
@@ -0,0 +1,374 @@
+// 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.Buffers;
+using System.Diagnostics;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    public class HttpRequestStreamReader : TextReader
+    {
+        private const int DefaultBufferSize = 1024;
+        private const int MinBufferSize = 128;
+        private const int MaxSharedBuilderCapacity = 360; // also the max capacity used in StringBuilderCache
+
+        private Stream _stream;
+        private readonly Encoding _encoding;
+        private readonly Decoder _decoder;
+
+        private readonly ArrayPool<byte> _bytePool;
+        private readonly ArrayPool<char> _charPool;
+
+        private readonly int _byteBufferSize;
+        private byte[] _byteBuffer;
+        private char[] _charBuffer;
+
+        private int _charBufferIndex;
+        private int _charsRead;
+        private int _bytesRead;
+
+        private bool _isBlocked;
+        private bool _disposed;
+
+        public HttpRequestStreamReader(Stream stream, Encoding encoding)
+            : this(stream, encoding, DefaultBufferSize, ArrayPool<byte>.Shared, ArrayPool<char>.Shared)
+        {
+        }
+
+        public HttpRequestStreamReader(Stream stream, Encoding encoding, int bufferSize)
+            : this(stream, encoding, bufferSize, ArrayPool<byte>.Shared, ArrayPool<char>.Shared)
+        {
+        }
+
+        public HttpRequestStreamReader(
+            Stream stream,
+            Encoding encoding,
+            int bufferSize,
+            ArrayPool<byte> bytePool,
+            ArrayPool<char> charPool)
+        {
+            _stream = stream ?? throw new ArgumentNullException(nameof(stream));
+            _encoding = encoding ?? throw new ArgumentNullException(nameof(encoding));
+            _bytePool = bytePool ?? throw new ArgumentNullException(nameof(bytePool));
+            _charPool = charPool ?? throw new ArgumentNullException(nameof(charPool));
+
+            if (bufferSize <= 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(bufferSize));
+            }
+            if (!stream.CanRead)
+            {
+                throw new ArgumentException(Resources.HttpRequestStreamReader_StreamNotReadable, nameof(stream));
+            }
+
+            _byteBufferSize = bufferSize;
+
+            _decoder = encoding.GetDecoder();
+            _byteBuffer = _bytePool.Rent(bufferSize);
+
+            try
+            {
+                var requiredLength = encoding.GetMaxCharCount(bufferSize);
+                _charBuffer = _charPool.Rent(requiredLength);
+            }
+            catch
+            {
+                _bytePool.Return(_byteBuffer);
+
+                if (_charBuffer != null)
+                {
+                    _charPool.Return(_charBuffer);
+                }
+
+                throw;
+            }
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && !_disposed)
+            {
+                _disposed = true;
+
+                _bytePool.Return(_byteBuffer);
+                _charPool.Return(_charBuffer);
+            }
+
+            base.Dispose(disposing);
+        }
+
+        public override int Peek()
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(HttpRequestStreamReader));
+            }
+
+            if (_charBufferIndex == _charsRead)
+            {
+                if (_isBlocked || ReadIntoBuffer() == 0)
+                {
+                    return -1;
+                }
+            }
+
+            return _charBuffer[_charBufferIndex];
+        }
+
+        public override int Read()
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(HttpRequestStreamReader));
+            }
+
+            if (_charBufferIndex == _charsRead)
+            {
+                if (ReadIntoBuffer() == 0)
+                {
+                    return -1;
+                }
+            }
+
+            return _charBuffer[_charBufferIndex++];
+        }
+
+        public override int Read(char[] buffer, int index, int count)
+        {
+            if (buffer == null)
+            {
+                throw new ArgumentNullException(nameof(buffer));
+            }
+
+            if (index < 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(index));
+            }
+
+            if (count < 0 || index + count > buffer.Length)
+            {
+                throw new ArgumentOutOfRangeException(nameof(count));
+            }
+
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(HttpRequestStreamReader));
+            }
+
+            var charsRead = 0;
+            while (count > 0)
+            {
+                var charsRemaining = _charsRead - _charBufferIndex;
+                if (charsRemaining == 0)
+                {
+                    charsRemaining = ReadIntoBuffer();
+                }
+
+                if (charsRemaining == 0)
+                {
+                    break;  // We're at EOF
+                }
+
+                if (charsRemaining > count)
+                {
+                    charsRemaining = count;
+                }
+
+                Buffer.BlockCopy(
+                    _charBuffer,
+                    _charBufferIndex * 2,
+                    buffer,
+                    (index + charsRead) * 2,
+                    charsRemaining * 2);
+                _charBufferIndex += charsRemaining;
+
+                charsRead += charsRemaining;
+                count -= charsRemaining;
+
+                // If we got back fewer chars than we asked for, then it's likely the underlying stream is blocked.
+                // Send the data back to the caller so they can process it.
+                if (_isBlocked)
+                {
+                    break;
+                }
+            }
+
+            return charsRead;
+        }
+
+        public override async Task<int> ReadAsync(char[] buffer, int index, int count)
+        {
+            if (buffer == null)
+            {
+                throw new ArgumentNullException(nameof(buffer));
+            }
+
+            if (index < 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(index));
+            }
+
+            if (count < 0 || index + count > buffer.Length)
+            {
+                throw new ArgumentOutOfRangeException(nameof(count));
+            }
+
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(HttpRequestStreamReader));
+            }
+
+            if (_charBufferIndex == _charsRead && await ReadIntoBufferAsync() == 0)
+            {
+                return 0;
+            }
+
+            var charsRead = 0;
+            while (count > 0)
+            {
+                // n is the characters available in _charBuffer
+                var n = _charsRead - _charBufferIndex;
+
+                // charBuffer is empty, let's read from the stream
+                if (n == 0)
+                {
+                    _charsRead = 0;
+                    _charBufferIndex = 0;
+                    _bytesRead = 0;
+
+                    // We loop here so that we read in enough bytes to yield at least 1 char.
+                    // We break out of the loop if the stream is blocked (EOF is reached).
+                    do
+                    {
+                        Debug.Assert(n == 0);
+                        _bytesRead = await _stream.ReadAsync(
+                            _byteBuffer,
+                            0,
+                            _byteBufferSize);
+                        if (_bytesRead == 0)  // EOF
+                        {
+                            _isBlocked = true;
+                            break;
+                        }
+
+                        // _isBlocked == whether we read fewer bytes than we asked for.
+                        _isBlocked = (_bytesRead < _byteBufferSize);
+
+                        Debug.Assert(n == 0);
+
+                        _charBufferIndex = 0;
+                        n = _decoder.GetChars(
+                            _byteBuffer,
+                            0,
+                            _bytesRead,
+                            _charBuffer,
+                            0);
+
+                        Debug.Assert(n > 0);
+
+                        _charsRead += n; // Number of chars in StreamReader's buffer.
+                    }
+                    while (n == 0);
+
+                    if (n == 0)
+                    {
+                        break; // We're at EOF
+                    }
+                }
+
+                // Got more chars in charBuffer than the user requested
+                if (n > count)
+                {
+                    n = count;
+                }
+
+                Buffer.BlockCopy(
+                    _charBuffer,
+                    _charBufferIndex * 2,
+                    buffer,
+                    (index + charsRead) * 2,
+                    n * 2);
+
+                _charBufferIndex += n;
+
+                charsRead += n;
+                count -= n;
+
+                // This function shouldn't block for an indefinite amount of time,
+                // or reading from a network stream won't work right.  If we got
+                // fewer bytes than we requested, then we want to break right here.
+                if (_isBlocked)
+                {
+                    break;
+                }
+            }
+
+            return charsRead;
+        }
+
+        private int ReadIntoBuffer()
+        {
+            _charsRead = 0;
+            _charBufferIndex = 0;
+            _bytesRead = 0;
+
+            do
+            {
+                _bytesRead = _stream.Read(_byteBuffer, 0, _byteBufferSize);
+                if (_bytesRead == 0)  // We're at EOF
+                {
+                    return _charsRead;
+                }
+
+                _isBlocked = (_bytesRead < _byteBufferSize);
+                _charsRead += _decoder.GetChars(
+                    _byteBuffer,
+                    0,
+                    _bytesRead,
+                    _charBuffer,
+                    _charsRead);
+            }
+            while (_charsRead == 0);
+
+            return _charsRead;
+        }
+
+        private async Task<int> ReadIntoBufferAsync()
+        {
+            _charsRead = 0;
+            _charBufferIndex = 0;
+            _bytesRead = 0;
+
+            do
+            {
+
+                _bytesRead = await _stream.ReadAsync(
+                    _byteBuffer,
+                    0,
+                    _byteBufferSize).ConfigureAwait(false);
+                if (_bytesRead == 0)
+                {
+                    // We're at EOF
+                    return _charsRead;
+                }
+
+                // _isBlocked == whether we read fewer bytes than we asked for.
+                _isBlocked = (_bytesRead < _byteBufferSize);
+
+                _charsRead += _decoder.GetChars(
+                    _byteBuffer,
+                    0,
+                    _bytesRead,
+                    _charBuffer,
+                    _charsRead);
+            }
+            while (_charsRead == 0);
+
+            return _charsRead;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/WebUtilities/src/HttpResponseStreamWriter.cs b/src/Http/WebUtilities/src/HttpResponseStreamWriter.cs
new file mode 100644
index 0000000000000000000000000000000000000000..050088ccb735b3c221b45d0db255779b54924ade
--- /dev/null
+++ b/src/Http/WebUtilities/src/HttpResponseStreamWriter.cs
@@ -0,0 +1,340 @@
+// 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.Buffers;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    /// <summary>
+    /// Writes to the <see cref="Stream"/> using the supplied <see cref="Encoding"/>.
+    /// It does not write the BOM and also does not close the stream.
+    /// </summary>
+    public class HttpResponseStreamWriter : TextWriter
+    {
+        private const int MinBufferSize = 128;
+        internal const int DefaultBufferSize = 16 * 1024;
+
+        private Stream _stream;
+        private readonly Encoder _encoder;
+        private readonly ArrayPool<byte> _bytePool;
+        private readonly ArrayPool<char> _charPool;
+        private readonly int _charBufferSize;
+
+        private byte[] _byteBuffer;
+        private char[] _charBuffer;
+
+        private int _charBufferCount;
+        private bool _disposed;
+
+        public HttpResponseStreamWriter(Stream stream, Encoding encoding)
+            : this(stream, encoding, DefaultBufferSize, ArrayPool<byte>.Shared, ArrayPool<char>.Shared)
+        {
+        }
+
+        public HttpResponseStreamWriter(Stream stream, Encoding encoding, int bufferSize)
+            : this(stream, encoding, bufferSize, ArrayPool<byte>.Shared, ArrayPool<char>.Shared)
+        {
+        }
+
+        public HttpResponseStreamWriter(
+            Stream stream,
+            Encoding encoding,
+            int bufferSize,
+            ArrayPool<byte> bytePool,
+            ArrayPool<char> charPool)
+        {
+            _stream = stream ?? throw new ArgumentNullException(nameof(stream));
+            Encoding = encoding ?? throw new ArgumentNullException(nameof(encoding));
+            _bytePool = bytePool ?? throw new ArgumentNullException(nameof(bytePool));
+            _charPool = charPool ?? throw new ArgumentNullException(nameof(charPool));
+
+            if (bufferSize <= 0)
+            {
+                throw new ArgumentOutOfRangeException(nameof(bufferSize));
+            }
+            if (!_stream.CanWrite)
+            {
+                throw new ArgumentException(Resources.HttpResponseStreamWriter_StreamNotWritable, nameof(stream));
+            }
+
+            _charBufferSize = bufferSize;
+
+            _encoder = encoding.GetEncoder();
+            _charBuffer = charPool.Rent(bufferSize);
+
+            try
+            {
+                var requiredLength = encoding.GetMaxByteCount(bufferSize);
+                _byteBuffer = bytePool.Rent(requiredLength);
+            }
+            catch
+            {
+                charPool.Return(_charBuffer);
+
+                if (_byteBuffer != null)
+                {
+                    bytePool.Return(_byteBuffer);
+                }
+
+                throw;
+            }
+        }
+
+        public override Encoding Encoding { get; }
+
+        public override void Write(char value)
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+            }
+
+            if (_charBufferCount == _charBufferSize)
+            {
+                FlushInternal(flushEncoder: false);
+            }
+
+            _charBuffer[_charBufferCount] = value;
+            _charBufferCount++;
+        }
+
+        public override void Write(char[] values, int index, int count)
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+            }
+
+            if (values == null)
+            {
+                return;
+            }
+
+            while (count > 0)
+            {
+                if (_charBufferCount == _charBufferSize)
+                {
+                    FlushInternal(flushEncoder: false);
+                }
+
+                CopyToCharBuffer(values, ref index, ref count);
+            }
+        }
+
+        public override void Write(string value)
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+            }
+
+            if (value == null)
+            {
+                return;
+            }
+
+            var count = value.Length;
+            var index = 0;
+            while (count > 0)
+            {
+                if (_charBufferCount == _charBufferSize)
+                {
+                    FlushInternal(flushEncoder: false);
+                }
+
+                CopyToCharBuffer(value, ref index, ref count);
+            }
+        }
+
+        public override async Task WriteAsync(char value)
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+            }
+
+            if (_charBufferCount == _charBufferSize)
+            {
+                await FlushInternalAsync(flushEncoder: false);
+            }
+
+            _charBuffer[_charBufferCount] = value;
+            _charBufferCount++;
+        }
+
+        public override async Task WriteAsync(char[] values, int index, int count)
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+            }
+
+            if (values == null)
+            {
+                return;
+            }
+
+            while (count > 0)
+            {
+                if (_charBufferCount == _charBufferSize)
+                {
+                    await FlushInternalAsync(flushEncoder: false);
+                }
+
+                CopyToCharBuffer(values, ref index, ref count);
+            }
+        }
+
+        public override async Task WriteAsync(string value)
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+            }
+
+            if (value == null)
+            {
+                return;
+            }
+
+            var count = value.Length;
+            var index = 0;
+            while (count > 0)
+            {
+                if (_charBufferCount == _charBufferSize)
+                {
+                    await FlushInternalAsync(flushEncoder: false);
+                }
+
+                CopyToCharBuffer(value, ref index, ref count);
+            }
+        }
+
+        // We want to flush the stream when Flush/FlushAsync is explicitly
+        // called by the user (example: from a Razor view).
+
+        public override void Flush()
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+            }
+
+            FlushInternal(flushEncoder: true);
+        }
+
+        public override Task FlushAsync()
+        {
+            if (_disposed)
+            {
+                throw new ObjectDisposedException(nameof(HttpResponseStreamWriter));
+            }
+
+            return FlushInternalAsync(flushEncoder: true);
+        }
+
+        protected override void Dispose(bool disposing)
+        {
+            if (disposing && !_disposed)
+            {
+                _disposed = true;
+                try
+                {
+                    FlushInternal(flushEncoder: true);
+                }
+                finally
+                {
+                    _bytePool.Return(_byteBuffer);
+                    _charPool.Return(_charBuffer);
+                }
+            }
+
+            base.Dispose(disposing);
+        }
+
+        // Note: our FlushInternal method does NOT flush the underlying stream. This would result in
+        // chunking.
+        private void FlushInternal(bool flushEncoder)
+        {
+            if (_charBufferCount == 0)
+            {
+                return;
+            }
+
+            var count = _encoder.GetBytes(
+                _charBuffer,
+                0,
+                _charBufferCount,
+                _byteBuffer,
+                0,
+                flush: flushEncoder);
+
+            _charBufferCount = 0;
+
+            if (count > 0)
+            {
+                _stream.Write(_byteBuffer, 0, count);
+            }
+        }
+
+        // Note: our FlushInternalAsync method does NOT flush the underlying stream. This would result in
+        // chunking.
+        private async Task FlushInternalAsync(bool flushEncoder)
+        {
+            if (_charBufferCount == 0)
+            {
+                return;
+            }
+
+            var count = _encoder.GetBytes(
+                _charBuffer,
+                0,
+                _charBufferCount,
+                _byteBuffer,
+                0,
+                flush: flushEncoder);
+
+            _charBufferCount = 0;
+
+            if (count > 0)
+            {
+                await _stream.WriteAsync(_byteBuffer, 0, count);
+            }
+        }
+
+        private void CopyToCharBuffer(string value, ref int index, ref int count)
+        {
+            var remaining = Math.Min(_charBufferSize - _charBufferCount, count);
+
+            value.CopyTo(
+                sourceIndex: index,
+                destination: _charBuffer,
+                destinationIndex: _charBufferCount,
+                count: remaining);
+
+            _charBufferCount += remaining;
+            index += remaining;
+            count -= remaining;
+        }
+
+        private void CopyToCharBuffer(char[] values, ref int index, ref int count)
+        {
+            var remaining = Math.Min(_charBufferSize - _charBufferCount, count);
+
+            Buffer.BlockCopy(
+                src: values,
+                srcOffset: index * sizeof(char),
+                dst: _charBuffer,
+                dstOffset: _charBufferCount * sizeof(char),
+                count: remaining * sizeof(char));
+
+            _charBufferCount += remaining;
+            index += remaining;
+            count -= remaining;
+        }
+    }
+}
diff --git a/src/Http/WebUtilities/src/KeyValueAccumulator.cs b/src/Http/WebUtilities/src/KeyValueAccumulator.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5ae402e5236cbdd721260de9d5d06875cec49f50
--- /dev/null
+++ b/src/Http/WebUtilities/src/KeyValueAccumulator.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.Collections.Generic;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    public struct KeyValueAccumulator
+    {
+        private Dictionary<string, StringValues> _accumulator;
+        private Dictionary<string, List<string>> _expandingAccumulator;
+
+        public void Append(string key, string value)
+        {
+            if (_accumulator == null)
+            {
+                _accumulator = new Dictionary<string, StringValues>(StringComparer.OrdinalIgnoreCase);
+            }
+
+            StringValues values;
+            if (_accumulator.TryGetValue(key, out values))
+            {
+                if (values.Count == 0)
+                {
+                    // Marker entry for this key to indicate entry already in expanding list dictionary
+                    _expandingAccumulator[key].Add(value);
+                }
+                else if (values.Count == 1)
+                {
+                    // Second value for this key
+                    _accumulator[key] = new string[] { values[0], value };
+                }
+                else
+                {
+                    // Third value for this key
+                    // Add zero count entry and move to data to expanding list dictionary
+                    _accumulator[key] = default(StringValues);
+
+                    if (_expandingAccumulator == null)
+                    {
+                        _expandingAccumulator = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
+                    }
+
+                    // Already 3 entries so use starting allocated as 8; then use List's expansion mechanism for more
+                    var list = new List<string>(8);
+                    var array = values.ToArray();
+
+                    list.Add(array[0]);
+                    list.Add(array[1]);
+                    list.Add(value);
+
+                    _expandingAccumulator[key] = list;
+                }
+            }
+            else
+            {
+                // First value for this key
+                _accumulator[key] = new StringValues(value);
+            }
+
+            ValueCount++;
+        }
+
+        public bool HasValues => ValueCount > 0;
+
+        public int KeyCount => _accumulator?.Count ?? 0;
+
+        public int ValueCount { get; private set; }
+
+        public Dictionary<string, StringValues> GetResults()
+        {
+            if (_expandingAccumulator != null)
+            {
+                // Coalesce count 3+ multi-value entries into _accumulator dictionary
+                foreach (var entry in _expandingAccumulator)
+                {
+                    _accumulator[entry.Key] = new StringValues(entry.Value.ToArray());
+                }
+            }
+
+            return _accumulator ?? new Dictionary<string, StringValues>(0, StringComparer.OrdinalIgnoreCase);
+        }
+    }
+}
diff --git a/src/Http/WebUtilities/src/Microsoft.AspNetCore.WebUtilities.csproj b/src/Http/WebUtilities/src/Microsoft.AspNetCore.WebUtilities.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..3c7d2d8255b63273d6fe4877acf4f991c6d21de5
--- /dev/null
+++ b/src/Http/WebUtilities/src/Microsoft.AspNetCore.WebUtilities.csproj
@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <Description>ASP.NET Core utilities, such as for working with forms, multipart messages, and query strings.</Description>
+    <TargetFramework>netstandard2.0</TargetFramework>
+    <DefineConstants>$(DefineConstants);WebEncoders_In_WebUtilities</DefineConstants>
+    <NoWarn>$(NoWarn);CS1591</NoWarn>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PackageTags>aspnetcore</PackageTags>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.Extensions.WebEncoders.Sources" PrivateAssets="All" />
+    <Reference Include="Microsoft.Net.Http.Headers" />
+    <Reference Include="System.Text.Encodings.Web" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/WebUtilities/src/MultipartBoundary.cs b/src/Http/WebUtilities/src/MultipartBoundary.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0da13038353f0e2d82f683a6577cb9816a23e8c9
--- /dev/null
+++ b/src/Http/WebUtilities/src/MultipartBoundary.cs
@@ -0,0 +1,72 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Text;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    internal class MultipartBoundary
+    {
+        private readonly int[] _skipTable = new int[256];
+        private readonly string _boundary;
+        private bool _expectLeadingCrlf;
+
+        public MultipartBoundary(string boundary, bool expectLeadingCrlf = true)
+        {
+            if (boundary == null)
+            {
+                throw new ArgumentNullException(nameof(boundary));
+            }
+
+            _boundary = boundary;
+            _expectLeadingCrlf = expectLeadingCrlf;
+            Initialize(_boundary, _expectLeadingCrlf);
+        }
+
+        private void Initialize(string boundary, bool expectLeadingCrlf)
+        {
+            if (expectLeadingCrlf)
+            {
+                BoundaryBytes = Encoding.UTF8.GetBytes("\r\n--" + boundary);
+            }
+            else
+            {
+                BoundaryBytes = Encoding.UTF8.GetBytes("--" + boundary);
+            }
+            FinalBoundaryLength = BoundaryBytes.Length + 2; // Include the final '--' terminator.
+
+            var length = BoundaryBytes.Length;
+            for (var i = 0; i < _skipTable.Length; ++i)
+            {
+                _skipTable[i] = length;
+            }
+            for (var i = 0; i < length; ++i)
+            {
+                _skipTable[BoundaryBytes[i]] = Math.Max(1, length - 1 - i);
+            }
+        }
+
+        public int GetSkipValue(byte input)
+        {
+            return _skipTable[input];
+        }
+
+        public bool ExpectLeadingCrlf
+        {
+            get { return _expectLeadingCrlf; }
+            set
+            {
+                if (value != _expectLeadingCrlf)
+                {
+                    _expectLeadingCrlf = value;
+                    Initialize(_boundary, _expectLeadingCrlf);
+                }
+            }
+        }
+
+        public byte[] BoundaryBytes { get; private set; }
+
+        public int FinalBoundaryLength { get; private set; }
+    }
+}
diff --git a/src/Http/WebUtilities/src/MultipartReader.cs b/src/Http/WebUtilities/src/MultipartReader.cs
new file mode 100644
index 0000000000000000000000000000000000000000..2da50a53600b8b49cea86a9403034f8ae2a4dd20
--- /dev/null
+++ b/src/Http/WebUtilities/src/MultipartReader.cs
@@ -0,0 +1,118 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    // https://www.ietf.org/rfc/rfc2046.txt
+    public class MultipartReader
+    {
+        public const int DefaultHeadersCountLimit = 16;
+        public const int DefaultHeadersLengthLimit = 1024 * 16;
+        private const int DefaultBufferSize = 1024 * 4;
+
+        private readonly BufferedReadStream _stream;
+        private readonly MultipartBoundary _boundary;
+        private MultipartReaderStream _currentStream;
+
+        public MultipartReader(string boundary, Stream stream)
+            : this(boundary, stream, DefaultBufferSize)
+        {
+        }
+
+        public MultipartReader(string boundary, Stream stream, int bufferSize)
+        {
+            if (boundary == null)
+            {
+                throw new ArgumentNullException(nameof(boundary));
+            }
+
+            if (stream == null)
+            {
+                throw new ArgumentNullException(nameof(stream));
+            }
+
+            if (bufferSize < boundary.Length + 8) // Size of the boundary + leading and trailing CRLF + leading and trailing '--' markers.
+            {
+                throw new ArgumentOutOfRangeException(nameof(bufferSize), bufferSize, "Insufficient buffer space, the buffer must be larger than the boundary: " + boundary);
+            }
+            _stream = new BufferedReadStream(stream, bufferSize);
+            _boundary = new MultipartBoundary(boundary, false);
+            // This stream will drain any preamble data and remove the first boundary marker.
+            // TODO: HeadersLengthLimit can't be modified until after the constructor.
+            _currentStream = new MultipartReaderStream(_stream, _boundary) { LengthLimit = HeadersLengthLimit };
+        }
+
+        /// <summary>
+        /// The limit for the number of headers to read.
+        /// </summary>
+        public int HeadersCountLimit { get; set; } = DefaultHeadersCountLimit;
+
+        /// <summary>
+        /// The combined size limit for headers per multipart section.
+        /// </summary>
+        public int HeadersLengthLimit { get; set; } = DefaultHeadersLengthLimit;
+
+        /// <summary>
+        /// The optional limit for the total response body length.
+        /// </summary>
+        public long? BodyLengthLimit { get; set; }
+
+        public async Task<MultipartSection> ReadNextSectionAsync(CancellationToken cancellationToken = new CancellationToken())
+        {
+            // Drain the prior section.
+            await _currentStream.DrainAsync(cancellationToken);
+            // If we're at the end return null
+            if (_currentStream.FinalBoundaryFound)
+            {
+                // There may be trailer data after the last boundary.
+                await _stream.DrainAsync(HeadersLengthLimit, cancellationToken);
+                return null;
+            }
+            var headers = await ReadHeadersAsync(cancellationToken);
+            _boundary.ExpectLeadingCrlf = true;
+            _currentStream = new MultipartReaderStream(_stream, _boundary) { LengthLimit = BodyLengthLimit };
+            long? baseStreamOffset = _stream.CanSeek ? (long?)_stream.Position : null;
+            return new MultipartSection() { Headers = headers, Body = _currentStream, BaseStreamOffset = baseStreamOffset };
+        }
+
+        private async Task<Dictionary<string, StringValues>> ReadHeadersAsync(CancellationToken cancellationToken)
+        {
+            int totalSize = 0;
+            var accumulator = new KeyValueAccumulator();
+            var line = await _stream.ReadLineAsync(HeadersLengthLimit - totalSize, cancellationToken);
+            while (!string.IsNullOrEmpty(line))
+            {
+                if (HeadersLengthLimit - totalSize < line.Length)
+                {
+                    throw new InvalidDataException($"Multipart headers length limit {HeadersLengthLimit} exceeded.");
+                }
+                totalSize += line.Length;
+                int splitIndex = line.IndexOf(':');
+                if (splitIndex <= 0)
+                {
+                    throw new InvalidDataException($"Invalid header line: {line}");
+                }
+
+                var name = line.Substring(0, splitIndex);
+                var value = line.Substring(splitIndex + 1, line.Length - splitIndex - 1).Trim();
+                accumulator.Append(name, value);
+                if (accumulator.KeyCount > HeadersCountLimit)
+                {
+                    throw new InvalidDataException($"Multipart headers count limit {HeadersCountLimit} exceeded.");
+                }
+
+                line = await _stream.ReadLineAsync(HeadersLengthLimit - totalSize, cancellationToken);
+            }
+
+            return accumulator.GetResults();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/WebUtilities/src/MultipartReaderStream.cs b/src/Http/WebUtilities/src/MultipartReaderStream.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7952bd34b2299824d191cd7f2268bf145abbee41
--- /dev/null
+++ b/src/Http/WebUtilities/src/MultipartReaderStream.cs
@@ -0,0 +1,336 @@
+// 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.Buffers;
+using System.Diagnostics;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    internal class MultipartReaderStream : Stream
+    {
+        private readonly MultipartBoundary _boundary;
+        private readonly BufferedReadStream _innerStream;
+        private readonly ArrayPool<byte> _bytePool;
+
+        private readonly long _innerOffset;
+        private long _position;
+        private long _observedLength;
+        private bool _finished;
+
+        /// <summary>
+        /// Creates a stream that reads until it reaches the given boundary pattern.
+        /// </summary>
+        /// <param name="stream">The <see cref="BufferedReadStream"/>.</param>
+        /// <param name="boundary">The boundary pattern to use.</param>
+        public MultipartReaderStream(BufferedReadStream stream, MultipartBoundary boundary)
+            : this(stream, boundary, ArrayPool<byte>.Shared)
+        {
+        }
+
+        /// <summary>
+        /// Creates a stream that reads until it reaches the given boundary pattern.
+        /// </summary>
+        /// <param name="stream">The <see cref="BufferedReadStream"/>.</param>
+        /// <param name="boundary">The boundary pattern to use.</param>
+        /// <param name="bytePool">The ArrayPool pool to use for temporary byte arrays.</param>
+        public MultipartReaderStream(BufferedReadStream stream, MultipartBoundary boundary, ArrayPool<byte> bytePool)
+        {
+            if (stream == null)
+            {
+                throw new ArgumentNullException(nameof(stream));
+            }
+
+            if (boundary == null)
+            {
+                throw new ArgumentNullException(nameof(boundary));
+            }
+
+            _bytePool = bytePool;
+            _innerStream = stream;
+            _innerOffset = _innerStream.CanSeek ? _innerStream.Position : 0;
+            _boundary = boundary;
+        }
+
+        public bool FinalBoundaryFound { get; private set; }
+
+        public long? LengthLimit { get; set; }
+
+        public override bool CanRead
+        {
+            get { return true; }
+        }
+
+        public override bool CanSeek
+        {
+            get { return _innerStream.CanSeek; }
+        }
+
+        public override bool CanWrite
+        {
+            get { return false; }
+        }
+
+        public override long Length
+        {
+            get { return _observedLength; }
+        }
+
+        public override long Position
+        {
+            get { return _position; }
+            set
+            {
+                if (value < 0)
+                {
+                    throw new ArgumentOutOfRangeException(nameof(value), value, "The Position must be positive.");
+                }
+                if (value > _observedLength)
+                {
+                    throw new ArgumentOutOfRangeException(nameof(value), value, "The Position must be less than length.");
+                }
+                _position = value;
+                if (_position < _observedLength)
+                {
+                    _finished = false;
+                }
+            }
+        }
+
+        public override long Seek(long offset, SeekOrigin origin)
+        {
+            if (origin == SeekOrigin.Begin)
+            {
+                Position = offset;
+            }
+            else if (origin == SeekOrigin.Current)
+            {
+                Position = Position + offset;
+            }
+            else // if (origin == SeekOrigin.End)
+            {
+                Position = Length + offset;
+            }
+            return Position;
+        }
+
+        public override void SetLength(long value)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override void Write(byte[] buffer, int offset, int count)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override void Flush()
+        {
+            throw new NotSupportedException();
+        }
+
+        private void PositionInnerStream()
+        {
+            if (_innerStream.CanSeek && _innerStream.Position != (_innerOffset + _position))
+            {
+                _innerStream.Position = _innerOffset + _position;
+            }
+        }
+
+        private int UpdatePosition(int read)
+        {
+            _position += read;
+            if (_observedLength < _position)
+            {
+                _observedLength = _position;
+                if (LengthLimit.HasValue && _observedLength > LengthLimit.Value)
+                {
+                    throw new InvalidDataException($"Multipart body length limit {LengthLimit.Value} exceeded.");
+                }
+            }
+            return read;
+        }
+
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            if (_finished)
+            {
+                return 0;
+            }
+
+            PositionInnerStream();
+            if (!_innerStream.EnsureBuffered(_boundary.FinalBoundaryLength))
+            {
+                throw new IOException("Unexpected end of Stream, the content may have already been read by another component. ");
+            }
+            var bufferedData = _innerStream.BufferedData;
+
+            // scan for a boundary match, full or partial.
+            int read;
+            if (SubMatch(bufferedData, _boundary.BoundaryBytes, out var matchOffset, out var matchCount))
+            {
+                // We found a possible match, return any data before it.
+                if (matchOffset > bufferedData.Offset)
+                {
+                    read = _innerStream.Read(buffer, offset, Math.Min(count, matchOffset - bufferedData.Offset));
+                    return UpdatePosition(read);
+                }
+
+                var length = _boundary.BoundaryBytes.Length;
+                Debug.Assert(matchCount == length);
+
+                // "The boundary may be followed by zero or more characters of
+                // linear whitespace. It is then terminated by either another CRLF"
+                // or -- for the final boundary.
+                var boundary = _bytePool.Rent(length);
+                read = _innerStream.Read(boundary, 0, length);
+                _bytePool.Return(boundary);
+                Debug.Assert(read == length); // It should have all been buffered
+
+                var remainder = _innerStream.ReadLine(lengthLimit: 100); // Whitespace may exceed the buffer.
+                remainder = remainder.Trim();
+                if (string.Equals("--", remainder, StringComparison.Ordinal))
+                {
+                    FinalBoundaryFound = true;
+                }
+                Debug.Assert(FinalBoundaryFound || string.Equals(string.Empty, remainder, StringComparison.Ordinal), "Un-expected data found on the boundary line: " + remainder);
+                _finished = true;
+                return 0;
+            }
+
+            // No possible boundary match within the buffered data, return the data from the buffer.
+            read = _innerStream.Read(buffer, offset, Math.Min(count, bufferedData.Count));
+            return UpdatePosition(read);
+        }
+
+        public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            if (_finished)
+            {
+                return 0;
+            }
+
+            PositionInnerStream();
+            if (!await _innerStream.EnsureBufferedAsync(_boundary.FinalBoundaryLength, cancellationToken))
+            {
+                throw new IOException("Unexpected end of Stream, the content may have already been read by another component. ");
+            }
+            var bufferedData = _innerStream.BufferedData;
+
+            // scan for a boundary match, full or partial.
+            int matchOffset;
+            int matchCount;
+            int read;
+            if (SubMatch(bufferedData, _boundary.BoundaryBytes, out matchOffset, out matchCount))
+            {
+                // We found a possible match, return any data before it.
+                if (matchOffset > bufferedData.Offset)
+                {
+                    // Sync, it's already buffered
+                    read = _innerStream.Read(buffer, offset, Math.Min(count, matchOffset - bufferedData.Offset));
+                    return UpdatePosition(read);
+                }
+
+                var length = _boundary.BoundaryBytes.Length;
+                Debug.Assert(matchCount == length);
+
+                // "The boundary may be followed by zero or more characters of
+                // linear whitespace. It is then terminated by either another CRLF"
+                // or -- for the final boundary.
+                var boundary = _bytePool.Rent(length);
+                read = _innerStream.Read(boundary, 0, length);
+                _bytePool.Return(boundary);
+                Debug.Assert(read == length); // It should have all been buffered
+
+                var remainder = await _innerStream.ReadLineAsync(lengthLimit: 100, cancellationToken: cancellationToken); // Whitespace may exceed the buffer.
+                remainder = remainder.Trim();
+                if (string.Equals("--", remainder, StringComparison.Ordinal))
+                {
+                    FinalBoundaryFound = true;
+                }
+                Debug.Assert(FinalBoundaryFound || string.Equals(string.Empty, remainder, StringComparison.Ordinal), "Un-expected data found on the boundary line: " + remainder);
+
+                _finished = true;
+                return 0;
+            }
+
+            // No possible boundary match within the buffered data, return the data from the buffer.
+            read = _innerStream.Read(buffer, offset, Math.Min(count, bufferedData.Count));
+            return UpdatePosition(read);
+        }
+
+        // Does segment1 contain all of matchBytes, or does it end with the start of matchBytes?
+        // 1: AAAAABBBBBCCCCC
+        // 2:      BBBBB
+        // Or:
+        // 1: AAAAABBB
+        // 2:      BBBBB
+        private bool SubMatch(ArraySegment<byte> segment1, byte[] matchBytes, out int matchOffset, out int matchCount)
+        {
+            // clear matchCount to zero
+            matchCount = 0;
+
+            // case 1: does segment1 fully contain matchBytes?
+            {
+                var matchBytesLengthMinusOne = matchBytes.Length - 1;
+                var matchBytesLastByte = matchBytes[matchBytesLengthMinusOne];
+                var segmentEndMinusMatchBytesLength = segment1.Offset + segment1.Count - matchBytes.Length;
+
+                matchOffset = segment1.Offset;
+                while (matchOffset < segmentEndMinusMatchBytesLength)
+                {
+                    var lookaheadTailChar = segment1.Array[matchOffset + matchBytesLengthMinusOne];
+                    if (lookaheadTailChar == matchBytesLastByte &&
+                        CompareBuffers(segment1.Array, matchOffset, matchBytes, 0, matchBytesLengthMinusOne) == 0)
+                    {
+                        matchCount = matchBytes.Length;
+                        return true;
+                    }
+                    matchOffset += _boundary.GetSkipValue(lookaheadTailChar);
+                }
+            }
+
+            // case 2: does segment1 end with the start of matchBytes?
+            var segmentEnd = segment1.Offset + segment1.Count;
+
+            matchCount = 0;
+            for (; matchOffset < segmentEnd; matchOffset++)
+            {
+                var countLimit = segmentEnd - matchOffset;
+                for (matchCount = 0; matchCount < matchBytes.Length && matchCount < countLimit; matchCount++)
+                {
+                    if (matchBytes[matchCount] != segment1.Array[matchOffset + matchCount])
+                    {
+                        matchCount = 0;
+                        break;
+                    }
+                }
+                if (matchCount > 0)
+                {
+                    break;
+                }
+            }
+            return matchCount > 0;
+        }
+
+        private static int CompareBuffers(byte[] buffer1, int offset1, byte[] buffer2, int offset2, int count)
+        {
+            for (; count-- > 0; offset1++, offset2++)
+            {
+                if (buffer1[offset1] != buffer2[offset2])
+                {
+                    return buffer1[offset1] - buffer2[offset2];
+                }
+            }
+            return 0;
+        }
+    }
+}
diff --git a/src/Http/WebUtilities/src/MultipartSection.cs b/src/Http/WebUtilities/src/MultipartSection.cs
new file mode 100644
index 0000000000000000000000000000000000000000..96138c630a10e58edbdbb8857d3714a207703ed6
--- /dev/null
+++ b/src/Http/WebUtilities/src/MultipartSection.cs
@@ -0,0 +1,48 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.IO;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    public class MultipartSection
+    {
+        public string ContentType
+        {
+            get
+            {
+                StringValues values;
+                if (Headers.TryGetValue("Content-Type", out values))
+                {
+                    return values;
+                }
+                return null;
+            }
+        }
+
+        public string ContentDisposition
+        {
+            get
+            {
+                StringValues values;
+                if (Headers.TryGetValue("Content-Disposition", out values))
+                {
+                    return values;
+                }
+                return null;
+            }
+        }
+
+        public Dictionary<string, StringValues> Headers { get; set; }
+
+        public Stream Body { get; set; }
+
+        /// <summary>
+        /// The position where the body starts in the total multipart body.
+        /// This may not be available if the total multipart body is not seekable.
+        /// </summary>
+        public long? BaseStreamOffset { get; set; }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/WebUtilities/src/MultipartSectionConverterExtensions.cs b/src/Http/WebUtilities/src/MultipartSectionConverterExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..826ced168eaa966de182aba9278dbf3cf2a6d32d
--- /dev/null
+++ b/src/Http/WebUtilities/src/MultipartSectionConverterExtensions.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;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    /// <summary>
+    /// Various extensions for converting multipart sections
+    /// </summary>
+    public static class MultipartSectionConverterExtensions
+    {
+        /// <summary>
+        /// Converts the section to a file section
+        /// </summary>
+        /// <param name="section">The section to convert</param>
+        /// <returns>A file section</returns>
+        public static FileMultipartSection AsFileSection(this MultipartSection section)
+        {
+            if (section == null)
+            {
+                throw new ArgumentNullException(nameof(section));
+            }
+
+            try
+            {
+                return new FileMultipartSection(section);
+            }
+            catch
+            {
+                return null;
+            }
+        }
+
+        /// <summary>
+        /// Converts the section to a form section
+        /// </summary>
+        /// <param name="section">The section to convert</param>
+        /// <returns>A form section</returns>
+        public static FormMultipartSection AsFormDataSection(this MultipartSection section)
+        {
+            if (section == null)
+            {
+                throw new ArgumentNullException(nameof(section));
+            }
+
+            try
+            {
+                return new FormMultipartSection(section);
+            }
+            catch
+            {
+                return null;
+            }
+        }
+
+        /// <summary>
+        /// Retrieves and parses the content disposition header from a section
+        /// </summary>
+        /// <param name="section">The section from which to retrieve</param>
+        /// <returns>A <see cref="ContentDispositionHeaderValue"/> if the header was found, null otherwise</returns>
+        public static ContentDispositionHeaderValue GetContentDispositionHeader(this MultipartSection section)
+        {
+            ContentDispositionHeaderValue header;
+            if (!ContentDispositionHeaderValue.TryParse(section.ContentDisposition, out header))
+            {
+                return null;
+            }
+
+            return header;
+        }
+    }
+}
diff --git a/src/Http/WebUtilities/src/MultipartSectionStreamExtensions.cs b/src/Http/WebUtilities/src/MultipartSectionStreamExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..463a8d88d6ab8ada047592820a77a54c790aa1e4
--- /dev/null
+++ b/src/Http/WebUtilities/src/MultipartSectionStreamExtensions.cs
@@ -0,0 +1,49 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Net.Http.Headers;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    /// <summary>
+    /// Various extension methods for dealing with the section body stream
+    /// </summary>
+    public static class MultipartSectionStreamExtensions
+    {
+        /// <summary>
+        /// Reads the body of the section as a string
+        /// </summary>
+        /// <param name="section">The section to read from</param>
+        /// <returns>The body steam as string</returns>
+        public static async Task<string> ReadAsStringAsync(this MultipartSection section)
+        {
+            if (section == null)
+            {
+                throw new ArgumentNullException(nameof(section));
+            }
+
+            MediaTypeHeaderValue sectionMediaType;
+            MediaTypeHeaderValue.TryParse(section.ContentType, out sectionMediaType);
+
+            Encoding streamEncoding = sectionMediaType?.Encoding;
+            if (streamEncoding == null || streamEncoding == Encoding.UTF7)
+            {
+                streamEncoding = Encoding.UTF8;
+            }
+
+            using (var reader = new StreamReader(
+                section.Body, 
+                streamEncoding,
+                detectEncodingFromByteOrderMarks: true,
+                bufferSize: 1024, 
+                leaveOpen: true))
+            {
+                return await reader.ReadToEndAsync();
+            }
+        } 
+    }
+}
diff --git a/src/Http/WebUtilities/src/Properties/AssemblyInfo.cs b/src/Http/WebUtilities/src/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000000000000000000000000000000000..aa80ef1d7e9fdae5987f7fc743c50d694a26e9d0
--- /dev/null
+++ b/src/Http/WebUtilities/src/Properties/AssemblyInfo.cs
@@ -0,0 +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.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.WebUtilities.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/Http/WebUtilities/src/QueryHelpers.cs b/src/Http/WebUtilities/src/QueryHelpers.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6bd1a0bb82eecd239713d975ccd70ebc1abe8d72
--- /dev/null
+++ b/src/Http/WebUtilities/src/QueryHelpers.cs
@@ -0,0 +1,191 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Text.Encodings.Web;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    public static class QueryHelpers
+    {
+        /// <summary>
+        /// Append the given query key and value to the URI.
+        /// </summary>
+        /// <param name="uri">The base URI.</param>
+        /// <param name="name">The name of the query key.</param>
+        /// <param name="value">The query value.</param>
+        /// <returns>The combined result.</returns>
+        public static string AddQueryString(string uri, string name, string value)
+        {
+            if (uri == null)
+            {
+                throw new ArgumentNullException(nameof(uri));
+            }
+
+            if (name == null)
+            {
+                throw new ArgumentNullException(nameof(name));
+            }
+
+            if (value == null)
+            {
+                throw new ArgumentNullException(nameof(value));
+            }
+
+            return AddQueryString(
+                uri, new[] { new KeyValuePair<string, string>(name, value) });
+        }
+
+        /// <summary>
+        /// Append the given query keys and values to the uri.
+        /// </summary>
+        /// <param name="uri">The base uri.</param>
+        /// <param name="queryString">A collection of name value query pairs to append.</param>
+        /// <returns>The combined result.</returns>
+        public static string AddQueryString(string uri, IDictionary<string, string> queryString)
+        {
+            if (uri == null)
+            {
+                throw new ArgumentNullException(nameof(uri));
+            }
+
+            if (queryString == null)
+            {
+                throw new ArgumentNullException(nameof(queryString));
+            }
+
+            return AddQueryString(uri, (IEnumerable<KeyValuePair<string, string>>)queryString);
+        }
+
+        private static string AddQueryString(
+            string uri,
+            IEnumerable<KeyValuePair<string, string>> queryString)
+        {
+            if (uri == null)
+            {
+                throw new ArgumentNullException(nameof(uri));
+            }
+
+            if (queryString == null)
+            {
+                throw new ArgumentNullException(nameof(queryString));
+            }
+
+            var anchorIndex = uri.IndexOf('#');
+            var uriToBeAppended = uri;
+            var anchorText = "";
+            // If there is an anchor, then the query string must be inserted before its first occurance.
+            if (anchorIndex != -1)
+            {
+                anchorText = uri.Substring(anchorIndex);
+                uriToBeAppended = uri.Substring(0, anchorIndex);
+            }
+
+            var queryIndex = uriToBeAppended.IndexOf('?');
+            var hasQuery = queryIndex != -1;
+
+            var sb = new StringBuilder();
+            sb.Append(uriToBeAppended);
+            foreach (var parameter in queryString)
+            {
+                sb.Append(hasQuery ? '&' : '?');
+                sb.Append(UrlEncoder.Default.Encode(parameter.Key));
+                sb.Append('=');
+                sb.Append(UrlEncoder.Default.Encode(parameter.Value));
+                hasQuery = true;
+            }
+
+            sb.Append(anchorText);
+            return sb.ToString();
+        }
+
+        /// <summary>
+        /// Parse a query string into its component key and value parts.
+        /// </summary>
+        /// <param name="queryString">The raw query string value, with or without the leading '?'.</param>
+        /// <returns>A collection of parsed keys and values.</returns>
+        public static Dictionary<string, StringValues> ParseQuery(string queryString)
+        {
+            var result = ParseNullableQuery(queryString);
+
+            if (result == null)
+            {
+                return new Dictionary<string, StringValues>();
+            }
+
+            return result;
+        }
+
+
+        /// <summary>
+        /// Parse a query string into its component key and value parts.
+        /// </summary>
+        /// <param name="queryString">The raw query string value, with or without the leading '?'.</param>
+        /// <returns>A collection of parsed keys and values, null if there are no entries.</returns>
+        public static Dictionary<string, StringValues> ParseNullableQuery(string queryString)
+        {
+            var accumulator = new KeyValueAccumulator();
+
+            if (string.IsNullOrEmpty(queryString) || queryString == "?")
+            {
+                return null;
+            }
+
+            int scanIndex = 0;
+            if (queryString[0] == '?')
+            {
+                scanIndex = 1;
+            }
+
+            int textLength = queryString.Length;
+            int equalIndex = queryString.IndexOf('=');
+            if (equalIndex == -1)
+            {
+                equalIndex = textLength;
+            }
+            while (scanIndex < textLength)
+            {
+                int delimiterIndex = queryString.IndexOf('&', scanIndex);
+                if (delimiterIndex == -1)
+                {
+                    delimiterIndex = textLength;
+                }
+                if (equalIndex < delimiterIndex)
+                {
+                    while (scanIndex != equalIndex && char.IsWhiteSpace(queryString[scanIndex]))
+                    {
+                        ++scanIndex;
+                    }
+                    string name = queryString.Substring(scanIndex, equalIndex - scanIndex);
+                    string value = queryString.Substring(equalIndex + 1, delimiterIndex - equalIndex - 1);
+                    accumulator.Append(
+                        Uri.UnescapeDataString(name.Replace('+', ' ')),
+                        Uri.UnescapeDataString(value.Replace('+', ' ')));
+                    equalIndex = queryString.IndexOf('=', delimiterIndex);
+                    if (equalIndex == -1)
+                    {
+                        equalIndex = textLength;
+                    }
+                }
+                else
+                {
+                    if (delimiterIndex > scanIndex)
+                    {
+                        accumulator.Append(queryString.Substring(scanIndex, delimiterIndex - scanIndex), string.Empty);
+                    }
+                }
+                scanIndex = delimiterIndex + 1;
+            }
+
+            if (!accumulator.HasValues)
+            {
+                return null;
+            }
+
+            return accumulator.GetResults();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/WebUtilities/src/ReasonPhrases.cs b/src/Http/WebUtilities/src/ReasonPhrases.cs
new file mode 100644
index 0000000000000000000000000000000000000000..3aab17079d0c21d1f89294a9aa2a91991a7a4a0c
--- /dev/null
+++ b/src/Http/WebUtilities/src/ReasonPhrases.cs
@@ -0,0 +1,87 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    public static class ReasonPhrases
+    {
+        // Status Codes listed at http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
+        private static IDictionary<int, string> Phrases = new Dictionary<int, string>()
+        {
+            { 100, "Continue" },
+            { 101, "Switching Protocols" },
+            { 102, "Processing" },
+
+            { 200, "OK" },
+            { 201, "Created" },
+            { 202, "Accepted" },
+            { 203, "Non-Authoritative Information" },
+            { 204, "No Content" },
+            { 205, "Reset Content" },
+            { 206, "Partial Content" },
+            { 207, "Multi-Status" },
+            { 208, "Already Reported" },
+            { 226, "IM Used" },
+
+            { 300, "Multiple Choices" },
+            { 301, "Moved Permanently" },
+            { 302, "Found" },
+            { 303, "See Other" },
+            { 304, "Not Modified" },
+            { 305, "Use Proxy" },
+            { 306, "Switch Proxy" },
+            { 307, "Temporary Redirect" },
+            { 308, "Permanent Redirect" },
+
+            { 400, "Bad Request" },
+            { 401, "Unauthorized" },
+            { 402, "Payment Required" },
+            { 403, "Forbidden" },
+            { 404, "Not Found" },
+            { 405, "Method Not Allowed" },
+            { 406, "Not Acceptable" },
+            { 407, "Proxy Authentication Required" },
+            { 408, "Request Timeout" },
+            { 409, "Conflict" },
+            { 410, "Gone" },
+            { 411, "Length Required" },
+            { 412, "Precondition Failed" },
+            { 413, "Payload Too Large" },
+            { 414, "URI Too Long" },
+            { 415, "Unsupported Media Type" },
+            { 416, "Range Not Satisfiable" },
+            { 417, "Expectation Failed" },
+            { 418, "I'm a teapot" },
+            { 419, "Authentication Timeout" },
+            { 421, "Misdirected Request" },
+            { 422, "Unprocessable Entity" },
+            { 423, "Locked" },
+            { 424, "Failed Dependency" },
+            { 426, "Upgrade Required" },
+            { 428, "Precondition Required" },
+            { 429, "Too Many Requests" },
+            { 431, "Request Header Fields Too Large" },
+            { 451, "Unavailable For Legal Reasons" },
+
+            { 500, "Internal Server Error" },
+            { 501, "Not Implemented" },
+            { 502, "Bad Gateway" },
+            { 503, "Service Unavailable" },
+            { 504, "Gateway Timeout" },
+            { 505, "HTTP Version Not Supported" },
+            { 506, "Variant Also Negotiates" },
+            { 507, "Insufficient Storage" },
+            { 508, "Loop Detected" },
+            { 510, "Not Extended" },
+            { 511, "Network Authentication Required" },
+        };
+
+        public static string GetReasonPhrase(int statusCode)
+        {
+            string phrase;
+            return Phrases.TryGetValue(statusCode, out phrase) ? phrase : string.Empty;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/WebUtilities/src/Resources.Designer.cs b/src/Http/WebUtilities/src/Resources.Designer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7972e005d03b53032dbc86dfd9228058c31c555f
--- /dev/null
+++ b/src/Http/WebUtilities/src/Resources.Designer.cs
@@ -0,0 +1,89 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.42000
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.AspNetCore.WebUtilities {
+    using System;
+    using System.Reflection;
+    
+    
+    /// <summary>
+    ///    A strongly-typed resource class, for looking up localized strings, etc.
+    /// </summary>
+    // This class was auto-generated by the StronglyTypedResourceBuilder
+    // class via a tool like ResGen or Visual Studio.
+    // To add or remove a member, edit your .ResX file then rerun ResGen
+    // with the /str option, or rebuild your VS project.
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///    Returns the cached ResourceManager instance used by this class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNetCore.WebUtilities.Resources", typeof(Resources).GetTypeInfo().Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///    Overrides the current thread's CurrentUICulture property for all
+        ///    resource lookups using this strongly typed resource class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+        
+        /// <summary>
+        ///    Looks up a localized string similar to The stream must support reading..
+        /// </summary>
+        internal static string HttpRequestStreamReader_StreamNotReadable {
+            get {
+                return ResourceManager.GetString("HttpRequestStreamReader_StreamNotReadable", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///    Looks up a localized string similar to The stream must support writing..
+        /// </summary>
+        internal static string HttpResponseStreamWriter_StreamNotWritable {
+            get {
+                return ResourceManager.GetString("HttpResponseStreamWriter_StreamNotWritable", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///    Looks up a localized string similar to Invalid {0}, {1} or {2} length..
+        /// </summary>
+        internal static string WebEncoders_InvalidCountOffsetOrLength {
+            get {
+                return ResourceManager.GetString("WebEncoders_InvalidCountOffsetOrLength", resourceCulture);
+            }
+        }
+    }
+}
diff --git a/src/Http/WebUtilities/src/Resources.resx b/src/Http/WebUtilities/src/Resources.resx
new file mode 100644
index 0000000000000000000000000000000000000000..a32d2db5cc6095aed8aa96d716d4e4580741b3ca
--- /dev/null
+++ b/src/Http/WebUtilities/src/Resources.resx
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!--
+    Microsoft ResX Schema
+
+    Version 2.0
+
+    The primary goals of this format is to allow a simple XML format
+    that is mostly human readable. The generation and parsing of the
+    various data types are done through the TypeConverter classes
+    associated with the data types.
+
+    Example:
+
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+
+    There are any number of "resheader" rows that contain simple
+    name/value pairs.
+
+    Each data row contains a name, and value. The row also contains a
+    type or mimetype. Type corresponds to a .NET class that support
+    text/value conversion through the TypeConverter architecture.
+    Classes that don't support this are serialized and stored with the
+    mimetype set.
+
+    The mimetype is used for serialized objects, and tells the
+    ResXResourceReader how to depersist the object. This is currently not
+    extensible. For a given mimetype the value must be set accordingly:
+
+    Note - application/x-microsoft.net.object.binary.base64 is the format
+    that the ResXResourceWriter will generate, however the reader can
+    read any of the formats listed below.
+
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="HttpRequestStreamReader_StreamNotReadable" xml:space="preserve">
+    <value>The stream must support reading.</value>
+  </data>
+  <data name="HttpResponseStreamWriter_StreamNotWritable" xml:space="preserve">
+    <value>The stream must support writing.</value>
+  </data>
+  <data name="WebEncoders_InvalidCountOffsetOrLength" xml:space="preserve">
+    <value>Invalid {0}, {1} or {2} length.</value>
+  </data>
+</root>
\ No newline at end of file
diff --git a/src/Http/WebUtilities/src/StreamHelperExtensions.cs b/src/Http/WebUtilities/src/StreamHelperExtensions.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e2c16a9cf234ca6d8908a009d97ef7a95705d007
--- /dev/null
+++ b/src/Http/WebUtilities/src/StreamHelperExtensions.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.Buffers;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    public static class StreamHelperExtensions
+    {
+        private const int _maxReadBufferSize = 1024 * 4;
+
+        public static Task DrainAsync(this Stream stream, CancellationToken cancellationToken)
+        {
+            return stream.DrainAsync(ArrayPool<byte>.Shared, null, cancellationToken);
+        }
+
+        public static Task DrainAsync(this Stream stream, long? limit, CancellationToken cancellationToken)
+        {
+            return stream.DrainAsync(ArrayPool<byte>.Shared, limit, cancellationToken);
+        }
+
+        public static async Task DrainAsync(this Stream stream, ArrayPool<byte> bytePool, long? limit, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+            var buffer = bytePool.Rent(_maxReadBufferSize);
+            long total = 0;
+            try
+            {
+                var read = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
+                while (read > 0)
+                {
+                    // Not all streams support cancellation directly.
+                    cancellationToken.ThrowIfCancellationRequested();
+                    if (limit.HasValue && limit.Value - total < read)
+                    {
+                        throw new InvalidDataException($"The stream exceeded the data limit {limit.Value}.");
+                    }
+                    total += read;
+                    read = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
+                }
+            }
+            finally
+            {
+                bytePool.Return(buffer);
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/WebUtilities/src/baseline.netcore.json b/src/Http/WebUtilities/src/baseline.netcore.json
new file mode 100644
index 0000000000000000000000000000000000000000..896fe0fcb3508bbf2adb40dc99ee87e7284d70ce
--- /dev/null
+++ b/src/Http/WebUtilities/src/baseline.netcore.json
@@ -0,0 +1,2272 @@
+{
+  "AssemblyIdentity": "Microsoft.AspNetCore.WebUtilities, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60",
+  "Types": [
+    {
+      "Name": "Microsoft.AspNetCore.WebUtilities.WebEncoders",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Base64UrlDecode",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Byte[]",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Base64UrlDecode",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "System.String"
+            },
+            {
+              "Name": "offset",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Byte[]",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Base64UrlDecode",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "System.String"
+            },
+            {
+              "Name": "offset",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "buffer",
+              "Type": "System.Char[]"
+            },
+            {
+              "Name": "bufferOffset",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Byte[]",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetArraySizeRequiredToDecode",
+          "Parameters": [
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Base64UrlEncode",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "System.Byte[]"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Base64UrlEncode",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "System.Byte[]"
+            },
+            {
+              "Name": "offset",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Base64UrlEncode",
+          "Parameters": [
+            {
+              "Name": "input",
+              "Type": "System.Byte[]"
+            },
+            {
+              "Name": "offset",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "output",
+              "Type": "System.Char[]"
+            },
+            {
+              "Name": "outputOffset",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetArraySizeRequiredToEncode",
+          "Parameters": [
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.WebUtilities.Base64UrlTextEncoder",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Encode",
+          "Parameters": [
+            {
+              "Name": "data",
+              "Type": "System.Byte[]"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Decode",
+          "Parameters": [
+            {
+              "Name": "text",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Byte[]",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.WebUtilities.BufferedReadStream",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "BaseType": "System.IO.Stream",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_BufferedData",
+          "Parameters": [],
+          "ReturnType": "System.ArraySegment<System.Byte>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_CanRead",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_CanSeek",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_CanTimeout",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_CanWrite",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Length",
+          "Parameters": [],
+          "ReturnType": "System.Int64",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Position",
+          "Parameters": [],
+          "ReturnType": "System.Int64",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Position",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int64"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Seek",
+          "Parameters": [
+            {
+              "Name": "offset",
+              "Type": "System.Int64"
+            },
+            {
+              "Name": "origin",
+              "Type": "System.IO.SeekOrigin"
+            }
+          ],
+          "ReturnType": "System.Int64",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SetLength",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int64"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Dispose",
+          "Parameters": [
+            {
+              "Name": "disposing",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Flush",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "FlushAsync",
+          "Parameters": [
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Write",
+          "Parameters": [
+            {
+              "Name": "buffer",
+              "Type": "System.Byte[]"
+            },
+            {
+              "Name": "offset",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "WriteAsync",
+          "Parameters": [
+            {
+              "Name": "buffer",
+              "Type": "System.Byte[]"
+            },
+            {
+              "Name": "offset",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Read",
+          "Parameters": [
+            {
+              "Name": "buffer",
+              "Type": "System.Byte[]"
+            },
+            {
+              "Name": "offset",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ReadAsync",
+          "Parameters": [
+            {
+              "Name": "buffer",
+              "Type": "System.Byte[]"
+            },
+            {
+              "Name": "offset",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Int32>",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "EnsureBuffered",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "EnsureBufferedAsync",
+          "Parameters": [
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Boolean>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "EnsureBuffered",
+          "Parameters": [
+            {
+              "Name": "minCount",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "EnsureBufferedAsync",
+          "Parameters": [
+            {
+              "Name": "minCount",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Boolean>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ReadLine",
+          "Parameters": [
+            {
+              "Name": "lengthLimit",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ReadLineAsync",
+          "Parameters": [
+            {
+              "Name": "lengthLimit",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.String>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "inner",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "bufferSize",
+              "Type": "System.Int32"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "inner",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "bufferSize",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "bytePool",
+              "Type": "System.Buffers.ArrayPool<System.Byte>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.WebUtilities.FileBufferingReadStream",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "BaseType": "System.IO.Stream",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_InMemory",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_TempFileName",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_CanRead",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_CanSeek",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_CanWrite",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Length",
+          "Parameters": [],
+          "ReturnType": "System.Int64",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Position",
+          "Parameters": [],
+          "ReturnType": "System.Int64",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Position",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int64"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Seek",
+          "Parameters": [
+            {
+              "Name": "offset",
+              "Type": "System.Int64"
+            },
+            {
+              "Name": "origin",
+              "Type": "System.IO.SeekOrigin"
+            }
+          ],
+          "ReturnType": "System.Int64",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Read",
+          "Parameters": [
+            {
+              "Name": "buffer",
+              "Type": "System.Byte[]"
+            },
+            {
+              "Name": "offset",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ReadAsync",
+          "Parameters": [
+            {
+              "Name": "buffer",
+              "Type": "System.Byte[]"
+            },
+            {
+              "Name": "offset",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Int32>",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Write",
+          "Parameters": [
+            {
+              "Name": "buffer",
+              "Type": "System.Byte[]"
+            },
+            {
+              "Name": "offset",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "WriteAsync",
+          "Parameters": [
+            {
+              "Name": "buffer",
+              "Type": "System.Byte[]"
+            },
+            {
+              "Name": "offset",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "SetLength",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int64"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Flush",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Dispose",
+          "Parameters": [
+            {
+              "Name": "disposing",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "inner",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "memoryThreshold",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "bufferLimit",
+              "Type": "System.Nullable<System.Int64>"
+            },
+            {
+              "Name": "tempFileDirectoryAccessor",
+              "Type": "System.Func<System.String>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "inner",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "memoryThreshold",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "bufferLimit",
+              "Type": "System.Nullable<System.Int64>"
+            },
+            {
+              "Name": "tempFileDirectoryAccessor",
+              "Type": "System.Func<System.String>"
+            },
+            {
+              "Name": "bytePool",
+              "Type": "System.Buffers.ArrayPool<System.Byte>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "inner",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "memoryThreshold",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "bufferLimit",
+              "Type": "System.Nullable<System.Int64>"
+            },
+            {
+              "Name": "tempFileDirectory",
+              "Type": "System.String"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "inner",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "memoryThreshold",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "bufferLimit",
+              "Type": "System.Nullable<System.Int64>"
+            },
+            {
+              "Name": "tempFileDirectory",
+              "Type": "System.String"
+            },
+            {
+              "Name": "bytePool",
+              "Type": "System.Buffers.ArrayPool<System.Byte>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.WebUtilities.FileMultipartSection",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Section",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.WebUtilities.MultipartSection",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_FileStream",
+          "Parameters": [],
+          "ReturnType": "System.IO.Stream",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Name",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_FileName",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "section",
+              "Type": "Microsoft.AspNetCore.WebUtilities.MultipartSection"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "section",
+              "Type": "Microsoft.AspNetCore.WebUtilities.MultipartSection"
+            },
+            {
+              "Name": "header",
+              "Type": "Microsoft.Net.Http.Headers.ContentDispositionHeaderValue"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.WebUtilities.FormMultipartSection",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Section",
+          "Parameters": [],
+          "ReturnType": "Microsoft.AspNetCore.WebUtilities.MultipartSection",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Name",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetValueAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task<System.String>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "section",
+              "Type": "Microsoft.AspNetCore.WebUtilities.MultipartSection"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "section",
+              "Type": "Microsoft.AspNetCore.WebUtilities.MultipartSection"
+            },
+            {
+              "Name": "header",
+              "Type": "Microsoft.Net.Http.Headers.ContentDispositionHeaderValue"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.WebUtilities.FormReader",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [
+        "System.IDisposable"
+      ],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_ValueCountLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ValueCountLimit",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_KeyLengthLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_KeyLengthLimit",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ValueLengthLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_ValueLengthLimit",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ReadNextPair",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Collections.Generic.KeyValuePair<System.String, System.String>>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ReadNextPairAsync",
+          "Parameters": [
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Nullable<System.Collections.Generic.KeyValuePair<System.String, System.String>>>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ReadForm",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.Dictionary<System.String, Microsoft.Extensions.Primitives.StringValues>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ReadFormAsync",
+          "Parameters": [
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Collections.Generic.Dictionary<System.String, Microsoft.Extensions.Primitives.StringValues>>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Dispose",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Sealed": true,
+          "Virtual": true,
+          "ImplementedInterface": "System.IDisposable",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "data",
+              "Type": "System.String"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "data",
+              "Type": "System.String"
+            },
+            {
+              "Name": "charPool",
+              "Type": "System.Buffers.ArrayPool<System.Char>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "stream",
+              "Type": "System.IO.Stream"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "stream",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "encoding",
+              "Type": "System.Text.Encoding"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "stream",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "encoding",
+              "Type": "System.Text.Encoding"
+            },
+            {
+              "Name": "charPool",
+              "Type": "System.Buffers.ArrayPool<System.Char>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "DefaultValueCountLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "1024"
+        },
+        {
+          "Kind": "Field",
+          "Name": "DefaultKeyLengthLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "2048"
+        },
+        {
+          "Kind": "Field",
+          "Name": "DefaultValueLengthLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "4194304"
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.WebUtilities.HttpRequestStreamReader",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "BaseType": "System.IO.TextReader",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Dispose",
+          "Parameters": [
+            {
+              "Name": "disposing",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Peek",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Read",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Read",
+          "Parameters": [
+            {
+              "Name": "buffer",
+              "Type": "System.Char[]"
+            },
+            {
+              "Name": "index",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Int32",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ReadAsync",
+          "Parameters": [
+            {
+              "Name": "buffer",
+              "Type": "System.Char[]"
+            },
+            {
+              "Name": "index",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.Int32>",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "stream",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "encoding",
+              "Type": "System.Text.Encoding"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "stream",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "encoding",
+              "Type": "System.Text.Encoding"
+            },
+            {
+              "Name": "bufferSize",
+              "Type": "System.Int32"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "stream",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "encoding",
+              "Type": "System.Text.Encoding"
+            },
+            {
+              "Name": "bufferSize",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "bytePool",
+              "Type": "System.Buffers.ArrayPool<System.Byte>"
+            },
+            {
+              "Name": "charPool",
+              "Type": "System.Buffers.ArrayPool<System.Char>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.WebUtilities.HttpResponseStreamWriter",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "BaseType": "System.IO.TextWriter",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_Encoding",
+          "Parameters": [],
+          "ReturnType": "System.Text.Encoding",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Write",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Char"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Write",
+          "Parameters": [
+            {
+              "Name": "values",
+              "Type": "System.Char[]"
+            },
+            {
+              "Name": "index",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Write",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "WriteAsync",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Char"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "WriteAsync",
+          "Parameters": [
+            {
+              "Name": "values",
+              "Type": "System.Char[]"
+            },
+            {
+              "Name": "index",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "count",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "WriteAsync",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Flush",
+          "Parameters": [],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "FlushAsync",
+          "Parameters": [],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "Dispose",
+          "Parameters": [
+            {
+              "Name": "disposing",
+              "Type": "System.Boolean"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Virtual": true,
+          "Override": true,
+          "Visibility": "Protected",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "stream",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "encoding",
+              "Type": "System.Text.Encoding"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "stream",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "encoding",
+              "Type": "System.Text.Encoding"
+            },
+            {
+              "Name": "bufferSize",
+              "Type": "System.Int32"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "stream",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "encoding",
+              "Type": "System.Text.Encoding"
+            },
+            {
+              "Name": "bufferSize",
+              "Type": "System.Int32"
+            },
+            {
+              "Name": "bytePool",
+              "Type": "System.Buffers.ArrayPool<System.Byte>"
+            },
+            {
+              "Name": "charPool",
+              "Type": "System.Buffers.ArrayPool<System.Char>"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.WebUtilities.KeyValueAccumulator",
+      "Visibility": "Public",
+      "Kind": "Struct",
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "Append",
+          "Parameters": [
+            {
+              "Name": "key",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HasValues",
+          "Parameters": [],
+          "ReturnType": "System.Boolean",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_KeyCount",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ValueCount",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetResults",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.Dictionary<System.String, Microsoft.Extensions.Primitives.StringValues>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.WebUtilities.MultipartReader",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_HeadersCountLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_HeadersCountLimit",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_HeadersLengthLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_HeadersLengthLimit",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_BodyLengthLimit",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Int64>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_BodyLengthLimit",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.Int64>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ReadNextSectionAsync",
+          "Parameters": [
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken",
+              "DefaultValue": "default(System.Threading.CancellationToken)"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<Microsoft.AspNetCore.WebUtilities.MultipartSection>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "boundary",
+              "Type": "System.String"
+            },
+            {
+              "Name": "stream",
+              "Type": "System.IO.Stream"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [
+            {
+              "Name": "boundary",
+              "Type": "System.String"
+            },
+            {
+              "Name": "stream",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "bufferSize",
+              "Type": "System.Int32"
+            }
+          ],
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Field",
+          "Name": "DefaultHeadersCountLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "16"
+        },
+        {
+          "Kind": "Field",
+          "Name": "DefaultHeadersLengthLimit",
+          "Parameters": [],
+          "ReturnType": "System.Int32",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": [],
+          "Constant": true,
+          "Literal": "16384"
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.WebUtilities.MultipartSection",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "get_ContentType",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_ContentDisposition",
+          "Parameters": [],
+          "ReturnType": "System.String",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Headers",
+          "Parameters": [],
+          "ReturnType": "System.Collections.Generic.Dictionary<System.String, Microsoft.Extensions.Primitives.StringValues>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Headers",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Collections.Generic.Dictionary<System.String, Microsoft.Extensions.Primitives.StringValues>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_Body",
+          "Parameters": [],
+          "ReturnType": "System.IO.Stream",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_Body",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.IO.Stream"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "get_BaseStreamOffset",
+          "Parameters": [],
+          "ReturnType": "System.Nullable<System.Int64>",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "set_BaseStreamOffset",
+          "Parameters": [
+            {
+              "Name": "value",
+              "Type": "System.Nullable<System.Int64>"
+            }
+          ],
+          "ReturnType": "System.Void",
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Constructor",
+          "Name": ".ctor",
+          "Parameters": [],
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.WebUtilities.MultipartSectionConverterExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "AsFileSection",
+          "Parameters": [
+            {
+              "Name": "section",
+              "Type": "Microsoft.AspNetCore.WebUtilities.MultipartSection"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.WebUtilities.FileMultipartSection",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AsFormDataSection",
+          "Parameters": [
+            {
+              "Name": "section",
+              "Type": "Microsoft.AspNetCore.WebUtilities.MultipartSection"
+            }
+          ],
+          "ReturnType": "Microsoft.AspNetCore.WebUtilities.FormMultipartSection",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "GetContentDispositionHeader",
+          "Parameters": [
+            {
+              "Name": "section",
+              "Type": "Microsoft.AspNetCore.WebUtilities.MultipartSection"
+            }
+          ],
+          "ReturnType": "Microsoft.Net.Http.Headers.ContentDispositionHeaderValue",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.WebUtilities.MultipartSectionStreamExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "ReadAsStringAsync",
+          "Parameters": [
+            {
+              "Name": "section",
+              "Type": "Microsoft.AspNetCore.WebUtilities.MultipartSection"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task<System.String>",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.WebUtilities.QueryHelpers",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "AddQueryString",
+          "Parameters": [
+            {
+              "Name": "uri",
+              "Type": "System.String"
+            },
+            {
+              "Name": "name",
+              "Type": "System.String"
+            },
+            {
+              "Name": "value",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "AddQueryString",
+          "Parameters": [
+            {
+              "Name": "uri",
+              "Type": "System.String"
+            },
+            {
+              "Name": "queryString",
+              "Type": "System.Collections.Generic.IDictionary<System.String, System.String>"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ParseQuery",
+          "Parameters": [
+            {
+              "Name": "queryString",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.Dictionary<System.String, Microsoft.Extensions.Primitives.StringValues>",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "ParseNullableQuery",
+          "Parameters": [
+            {
+              "Name": "queryString",
+              "Type": "System.String"
+            }
+          ],
+          "ReturnType": "System.Collections.Generic.Dictionary<System.String, Microsoft.Extensions.Primitives.StringValues>",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.WebUtilities.ReasonPhrases",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "GetReasonPhrase",
+          "Parameters": [
+            {
+              "Name": "statusCode",
+              "Type": "System.Int32"
+            }
+          ],
+          "ReturnType": "System.String",
+          "Static": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    },
+    {
+      "Name": "Microsoft.AspNetCore.WebUtilities.StreamHelperExtensions",
+      "Visibility": "Public",
+      "Kind": "Class",
+      "Abstract": true,
+      "Static": true,
+      "Sealed": true,
+      "ImplementedInterfaces": [],
+      "Members": [
+        {
+          "Kind": "Method",
+          "Name": "DrainAsync",
+          "Parameters": [
+            {
+              "Name": "stream",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "DrainAsync",
+          "Parameters": [
+            {
+              "Name": "stream",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "limit",
+              "Type": "System.Nullable<System.Int64>"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        },
+        {
+          "Kind": "Method",
+          "Name": "DrainAsync",
+          "Parameters": [
+            {
+              "Name": "stream",
+              "Type": "System.IO.Stream"
+            },
+            {
+              "Name": "bytePool",
+              "Type": "System.Buffers.ArrayPool<System.Byte>"
+            },
+            {
+              "Name": "limit",
+              "Type": "System.Nullable<System.Int64>"
+            },
+            {
+              "Name": "cancellationToken",
+              "Type": "System.Threading.CancellationToken"
+            }
+          ],
+          "ReturnType": "System.Threading.Tasks.Task",
+          "Static": true,
+          "Extension": true,
+          "Visibility": "Public",
+          "GenericParameter": []
+        }
+      ],
+      "GenericParameters": []
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/Http/WebUtilities/test/FileBufferingReadStreamTests.cs b/src/Http/WebUtilities/test/FileBufferingReadStreamTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..a83f1574eb50a1147b691b97da7082f998bfd66d
--- /dev/null
+++ b/src/Http/WebUtilities/test/FileBufferingReadStreamTests.cs
@@ -0,0 +1,299 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    public class FileBufferingReadStreamTests
+    {
+        private Stream MakeStream(int size)
+        {
+            // TODO: Fill with random data? Make readonly?
+            return new MemoryStream(new byte[size]);
+        }
+
+        [Fact]
+        public void FileBufferingReadStream_Properties_ExpectedValues()
+        {
+            var inner = MakeStream(1024 * 2);
+            using (var stream = new FileBufferingReadStream(inner, 1024, null, Directory.GetCurrentDirectory()))
+            {
+                Assert.True(stream.CanRead);
+                Assert.True(stream.CanSeek);
+                Assert.False(stream.CanWrite);
+                Assert.Equal(0, stream.Length); // Nothing buffered yet
+                Assert.Equal(0, stream.Position);
+                Assert.True(stream.InMemory);
+                Assert.Null(stream.TempFileName);
+            }
+        }
+
+        [Fact]
+        public void FileBufferingReadStream_SyncReadUnderThreshold_DoesntCreateFile()
+        {
+            var inner = MakeStream(1024 * 2);
+            using (var stream = new FileBufferingReadStream(inner, 1024 * 3, null, Directory.GetCurrentDirectory()))
+            {
+                var bytes = new byte[1000];
+                var read0 = stream.Read(bytes, 0, bytes.Length);
+                Assert.Equal(bytes.Length, read0);
+                Assert.Equal(read0, stream.Length);
+                Assert.Equal(read0, stream.Position);
+                Assert.True(stream.InMemory);
+                Assert.Null(stream.TempFileName);
+
+                var read1 = stream.Read(bytes, 0, bytes.Length);
+                Assert.Equal(bytes.Length, read1);
+                Assert.Equal(read0 + read1, stream.Length);
+                Assert.Equal(read0 + read1, stream.Position);
+                Assert.True(stream.InMemory);
+                Assert.Null(stream.TempFileName);
+
+                var read2 = stream.Read(bytes, 0, bytes.Length);
+                Assert.Equal(inner.Length - read0 - read1, read2);
+                Assert.Equal(read0 + read1 + read2, stream.Length);
+                Assert.Equal(read0 + read1 + read2, stream.Position);
+                Assert.True(stream.InMemory);
+                Assert.Null(stream.TempFileName);
+
+                var read3 = stream.Read(bytes, 0, bytes.Length);
+                Assert.Equal(0, read3);
+            }
+        }
+
+        [Fact]
+        public void FileBufferingReadStream_SyncReadOverThreshold_CreatesFile()
+        {
+            var inner = MakeStream(1024 * 2);
+            string tempFileName;
+            using (var stream = new FileBufferingReadStream(inner, 1024, null, GetCurrentDirectory()))
+            {
+                var bytes = new byte[1000];
+                var read0 = stream.Read(bytes, 0, bytes.Length);
+                Assert.Equal(bytes.Length, read0);
+                Assert.Equal(read0, stream.Length);
+                Assert.Equal(read0, stream.Position);
+                Assert.True(stream.InMemory);
+                Assert.Null(stream.TempFileName);
+
+                var read1 = stream.Read(bytes, 0, bytes.Length);
+                Assert.Equal(bytes.Length, read1);
+                Assert.Equal(read0 + read1, stream.Length);
+                Assert.Equal(read0 + read1, stream.Position);
+                Assert.False(stream.InMemory);
+                Assert.NotNull(stream.TempFileName);
+                tempFileName = stream.TempFileName;
+                Assert.True(File.Exists(tempFileName));
+
+                var read2 = stream.Read(bytes, 0, bytes.Length);
+                Assert.Equal(inner.Length - read0 - read1, read2);
+                Assert.Equal(read0 + read1 + read2, stream.Length);
+                Assert.Equal(read0 + read1 + read2, stream.Position);
+                Assert.False(stream.InMemory);
+                Assert.NotNull(stream.TempFileName);
+                Assert.True(File.Exists(tempFileName));
+
+                var read3 = stream.Read(bytes, 0, bytes.Length);
+                Assert.Equal(0, read3);
+            }
+
+            Assert.False(File.Exists(tempFileName));
+        }
+
+        [Fact]
+        public void FileBufferingReadStream_SyncReadWithInMemoryLimit_EnforcesLimit()
+        {
+            var inner = MakeStream(1024 * 2);
+            using (var stream = new FileBufferingReadStream(inner, 1024, 900, Directory.GetCurrentDirectory()))
+            {
+                var bytes = new byte[500];
+                var read0 = stream.Read(bytes, 0, bytes.Length);
+                Assert.Equal(bytes.Length, read0);
+                Assert.Equal(read0, stream.Length);
+                Assert.Equal(read0, stream.Position);
+                Assert.True(stream.InMemory);
+                Assert.Null(stream.TempFileName);
+
+                var exception = Assert.Throws<IOException>(() => stream.Read(bytes, 0, bytes.Length));
+                Assert.Equal("Buffer limit exceeded.", exception.Message);
+                Assert.True(stream.InMemory);
+                Assert.Null(stream.TempFileName);
+                Assert.False(File.Exists(stream.TempFileName));
+            }
+        }
+
+        [Fact]
+        public void FileBufferingReadStream_SyncReadWithOnDiskLimit_EnforcesLimit()
+        {
+            var inner = MakeStream(1024 * 2);
+            string tempFileName;
+            using (var stream = new FileBufferingReadStream(inner, 512, 1024, GetCurrentDirectory()))
+            {
+                var bytes = new byte[500];
+                var read0 = stream.Read(bytes, 0, bytes.Length);
+                Assert.Equal(bytes.Length, read0);
+                Assert.Equal(read0, stream.Length);
+                Assert.Equal(read0, stream.Position);
+                Assert.True(stream.InMemory);
+                Assert.Null(stream.TempFileName);
+
+                var read1 = stream.Read(bytes, 0, bytes.Length);
+                Assert.Equal(bytes.Length, read1);
+                Assert.Equal(read0 + read1, stream.Length);
+                Assert.Equal(read0 + read1, stream.Position);
+                Assert.False(stream.InMemory);
+                Assert.NotNull(stream.TempFileName);
+                tempFileName = stream.TempFileName;
+                Assert.True(File.Exists(tempFileName));
+
+                var exception = Assert.Throws<IOException>(() => stream.Read(bytes, 0, bytes.Length));
+                Assert.Equal("Buffer limit exceeded.", exception.Message);
+                Assert.False(stream.InMemory);
+                Assert.NotNull(stream.TempFileName);
+                Assert.False(File.Exists(tempFileName));
+            }
+
+            Assert.False(File.Exists(tempFileName));
+        }
+
+        ///////////////////
+
+        [Fact]
+        public async Task FileBufferingReadStream_AsyncReadUnderThreshold_DoesntCreateFile()
+        {
+            var inner = MakeStream(1024 * 2);
+            using (var stream = new FileBufferingReadStream(inner, 1024 * 3, null, Directory.GetCurrentDirectory()))
+            {
+                var bytes = new byte[1000];
+                var read0 = await stream.ReadAsync(bytes, 0, bytes.Length);
+                Assert.Equal(bytes.Length, read0);
+                Assert.Equal(read0, stream.Length);
+                Assert.Equal(read0, stream.Position);
+                Assert.True(stream.InMemory);
+                Assert.Null(stream.TempFileName);
+
+                var read1 = await stream.ReadAsync(bytes, 0, bytes.Length);
+                Assert.Equal(bytes.Length, read1);
+                Assert.Equal(read0 + read1, stream.Length);
+                Assert.Equal(read0 + read1, stream.Position);
+                Assert.True(stream.InMemory);
+                Assert.Null(stream.TempFileName);
+
+                var read2 = await stream.ReadAsync(bytes, 0, bytes.Length);
+                Assert.Equal(inner.Length - read0 - read1, read2);
+                Assert.Equal(read0 + read1 + read2, stream.Length);
+                Assert.Equal(read0 + read1 + read2, stream.Position);
+                Assert.True(stream.InMemory);
+                Assert.Null(stream.TempFileName);
+
+                var read3 = await stream.ReadAsync(bytes, 0, bytes.Length);
+                Assert.Equal(0, read3);
+            }
+        }
+
+        [Fact]
+        public async Task FileBufferingReadStream_AsyncReadOverThreshold_CreatesFile()
+        {
+            var inner = MakeStream(1024 * 2);
+            string tempFileName;
+            using (var stream = new FileBufferingReadStream(inner, 1024, null, GetCurrentDirectory()))
+            {
+                var bytes = new byte[1000];
+                var read0 = await stream.ReadAsync(bytes, 0, bytes.Length);
+                Assert.Equal(bytes.Length, read0);
+                Assert.Equal(read0, stream.Length);
+                Assert.Equal(read0, stream.Position);
+                Assert.True(stream.InMemory);
+                Assert.Null(stream.TempFileName);
+
+                var read1 = await stream.ReadAsync(bytes, 0, bytes.Length);
+                Assert.Equal(bytes.Length, read1);
+                Assert.Equal(read0 + read1, stream.Length);
+                Assert.Equal(read0 + read1, stream.Position);
+                Assert.False(stream.InMemory);
+                Assert.NotNull(stream.TempFileName);
+                tempFileName = stream.TempFileName;
+                Assert.True(File.Exists(tempFileName));
+
+                var read2 = await stream.ReadAsync(bytes, 0, bytes.Length);
+                Assert.Equal(inner.Length - read0 - read1, read2);
+                Assert.Equal(read0 + read1 + read2, stream.Length);
+                Assert.Equal(read0 + read1 + read2, stream.Position);
+                Assert.False(stream.InMemory);
+                Assert.NotNull(stream.TempFileName);
+                Assert.True(File.Exists(tempFileName));
+
+                var read3 = await stream.ReadAsync(bytes, 0, bytes.Length);
+                Assert.Equal(0, read3);
+            }
+
+            Assert.False(File.Exists(tempFileName));
+        }
+
+        [Fact]
+        public async Task FileBufferingReadStream_AsyncReadWithInMemoryLimit_EnforcesLimit()
+        {
+            var inner = MakeStream(1024 * 2);
+            using (var stream = new FileBufferingReadStream(inner, 1024, 900, Directory.GetCurrentDirectory()))
+            {
+                var bytes = new byte[500];
+                var read0 = await stream.ReadAsync(bytes, 0, bytes.Length);
+                Assert.Equal(bytes.Length, read0);
+                Assert.Equal(read0, stream.Length);
+                Assert.Equal(read0, stream.Position);
+                Assert.True(stream.InMemory);
+                Assert.Null(stream.TempFileName);
+
+                var exception = await Assert.ThrowsAsync<IOException>(() => stream.ReadAsync(bytes, 0, bytes.Length));
+                Assert.Equal("Buffer limit exceeded.", exception.Message);
+                Assert.True(stream.InMemory);
+                Assert.Null(stream.TempFileName);
+                Assert.False(File.Exists(stream.TempFileName));
+            }
+        }
+
+        [Fact]
+        public async Task FileBufferingReadStream_AsyncReadWithOnDiskLimit_EnforcesLimit()
+        {
+            var inner = MakeStream(1024 * 2);
+            string tempFileName;
+            using (var stream = new FileBufferingReadStream(inner, 512, 1024, GetCurrentDirectory()))
+            {
+                var bytes = new byte[500];
+                var read0 = await stream.ReadAsync(bytes, 0, bytes.Length);
+                Assert.Equal(bytes.Length, read0);
+                Assert.Equal(read0, stream.Length);
+                Assert.Equal(read0, stream.Position);
+                Assert.True(stream.InMemory);
+                Assert.Null(stream.TempFileName);
+
+                var read1 = await stream.ReadAsync(bytes, 0, bytes.Length);
+                Assert.Equal(bytes.Length, read1);
+                Assert.Equal(read0 + read1, stream.Length);
+                Assert.Equal(read0 + read1, stream.Position);
+                Assert.False(stream.InMemory);
+                Assert.NotNull(stream.TempFileName);
+                tempFileName = stream.TempFileName;
+                Assert.True(File.Exists(tempFileName));
+
+                var exception = await Assert.ThrowsAsync<IOException>(() => stream.ReadAsync(bytes, 0, bytes.Length));
+                Assert.Equal("Buffer limit exceeded.", exception.Message);
+                Assert.False(stream.InMemory);
+                Assert.NotNull(stream.TempFileName);
+                Assert.False(File.Exists(tempFileName));
+            }
+
+            Assert.False(File.Exists(tempFileName));
+        }
+
+        private static string GetCurrentDirectory()
+        {
+            return AppContext.BaseDirectory;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/WebUtilities/test/FormReaderAsyncTest.cs b/src/Http/WebUtilities/test/FormReaderAsyncTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0a7b5e20a9c4805a870eb18e82554a459fa95c88
--- /dev/null
+++ b/src/Http/WebUtilities/test/FormReaderAsyncTest.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 System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    public class FormReaderAsyncTest : FormReaderTests
+    {
+        protected override async Task<Dictionary<string, StringValues>> ReadFormAsync(FormReader reader)
+        {
+            return await reader.ReadFormAsync();
+        }
+
+        protected override async Task<KeyValuePair<string, string>?> ReadPair(FormReader reader)
+        {
+            return await reader.ReadNextPairAsync();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/WebUtilities/test/FormReaderTests.cs b/src/Http/WebUtilities/test/FormReaderTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..134efbb51571aad094112e78da460a92672a7f08
--- /dev/null
+++ b/src/Http/WebUtilities/test/FormReaderTests.cs
@@ -0,0 +1,230 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Primitives;
+using Xunit;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    public class FormReaderTests
+    {
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_EmptyKeyAtEndAllowed(bool bufferRequest)
+        {
+            var body = MakeStream(bufferRequest, "=bar");
+
+            var formCollection = await ReadFormAsync(new FormReader(body));
+
+            Assert.Equal("bar", formCollection[""].ToString());
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_EmptyKeyWithAdditionalEntryAllowed(bool bufferRequest)
+        {
+            var body = MakeStream(bufferRequest, "=bar&baz=2");
+
+            var formCollection = await ReadFormAsync(new FormReader(body));
+
+            Assert.Equal("bar", formCollection[""].ToString());
+            Assert.Equal("2", formCollection["baz"].ToString());
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_EmptyValuedAtEndAllowed(bool bufferRequest)
+        {
+            var body = MakeStream(bufferRequest, "foo=");
+
+            var formCollection = await ReadFormAsync(new FormReader(body));
+
+            Assert.Equal("", formCollection["foo"].ToString());
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_EmptyValuedWithAdditionalEntryAllowed(bool bufferRequest)
+        {
+            var body = MakeStream(bufferRequest, "foo=&baz=2");
+
+            var formCollection = await ReadFormAsync(new FormReader(body));
+
+            Assert.Equal("", formCollection["foo"].ToString());
+            Assert.Equal("2", formCollection["baz"].ToString());
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_ValueCountLimitMet_Success(bool bufferRequest)
+        {
+            var body = MakeStream(bufferRequest, "foo=1&bar=2&baz=3");
+
+            var formCollection = await ReadFormAsync(new FormReader(body) { ValueCountLimit = 3 });
+
+            Assert.Equal("1", formCollection["foo"].ToString());
+            Assert.Equal("2", formCollection["bar"].ToString());
+            Assert.Equal("3", formCollection["baz"].ToString());
+            Assert.Equal(3, formCollection.Count);
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_ValueCountLimitExceeded_Throw(bool bufferRequest)
+        {
+            var body = MakeStream(bufferRequest, "foo=1&baz=2&bar=3&baz=4&baf=5");
+
+            var exception = await Assert.ThrowsAsync<InvalidDataException>(
+                () => ReadFormAsync(new FormReader(body) { ValueCountLimit = 3 }));
+            Assert.Equal("Form value count limit 3 exceeded.", exception.Message);
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_ValueCountLimitExceededSameKey_Throw(bool bufferRequest)
+        {
+            var body = MakeStream(bufferRequest, "baz=1&baz=2&baz=3&baz=4");
+
+            var exception = await Assert.ThrowsAsync<InvalidDataException>(
+                () => ReadFormAsync(new FormReader(body) { ValueCountLimit = 3 }));
+            Assert.Equal("Form value count limit 3 exceeded.", exception.Message);
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_KeyLengthLimitMet_Success(bool bufferRequest)
+        {
+            var body = MakeStream(bufferRequest, "foo=1&bar=2&baz=3&baz=4");
+
+            var formCollection = await ReadFormAsync(new FormReader(body) { KeyLengthLimit = 10 });
+
+            Assert.Equal("1", formCollection["foo"].ToString());
+            Assert.Equal("2", formCollection["bar"].ToString());
+            Assert.Equal("3,4", formCollection["baz"].ToString());
+            Assert.Equal(3, formCollection.Count);
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_KeyLengthLimitExceeded_Throw(bool bufferRequest)
+        {
+            var body = MakeStream(bufferRequest, "foo=1&baz1234567890=2");
+
+            var exception = await Assert.ThrowsAsync<InvalidDataException>(
+                () => ReadFormAsync(new FormReader(body) { KeyLengthLimit = 10 }));
+            Assert.Equal("Form key or value length limit 10 exceeded.", exception.Message);
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_ValueLengthLimitMet_Success(bool bufferRequest)
+        {
+            var body = MakeStream(bufferRequest, "foo=1&bar=1234567890&baz=3&baz=4");
+
+            var formCollection = await ReadFormAsync(new FormReader(body) { ValueLengthLimit = 10 });
+
+            Assert.Equal("1", formCollection["foo"].ToString());
+            Assert.Equal("1234567890", formCollection["bar"].ToString());
+            Assert.Equal("3,4", formCollection["baz"].ToString());
+            Assert.Equal(3, formCollection.Count);
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadFormAsync_ValueLengthLimitExceeded_Throw(bool bufferRequest)
+        {
+            var body = MakeStream(bufferRequest, "foo=1&baz=1234567890123");
+
+            var exception = await Assert.ThrowsAsync<InvalidDataException>(
+                () => ReadFormAsync(new FormReader(body) { ValueLengthLimit = 10 }));
+            Assert.Equal("Form key or value length limit 10 exceeded.", exception.Message);
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadNextPair_ReadsAllPairs(bool bufferRequest)
+        {
+            var body = MakeStream(bufferRequest, "foo=&baz=2");
+
+            var reader = new FormReader(body);
+
+            var pair = (KeyValuePair<string, string>)await ReadPair(reader);
+
+            Assert.Equal("foo", pair.Key);
+            Assert.Equal("", pair.Value);
+
+            pair = (KeyValuePair<string, string>)await ReadPair(reader);
+
+            Assert.Equal("baz", pair.Key);
+            Assert.Equal("2", pair.Value);
+
+            Assert.Null(await ReadPair(reader));
+        }
+
+        [Theory]
+        [InlineData(true)]
+        [InlineData(false)]
+        public async Task ReadNextPair_ReturnsNullOnEmptyStream(bool bufferRequest)
+        {
+            var body = MakeStream(bufferRequest, "");
+
+            var reader = new FormReader(body);
+
+            Assert.Null(await ReadPair(reader));
+        }
+
+        // https://en.wikipedia.org/wiki/Percent-encoding
+        [Theory]
+        [InlineData("++=hello", "  ", "hello")]
+        [InlineData("a=1+1", "a", "1 1")]
+        [InlineData("%22%25%2D%2E%3C%3E%5C%5E%5F%60%7B%7C%7D%7E=%22%25%2D%2E%3C%3E%5C%5E%5F%60%7B%7C%7D%7E", "\"%-.<>\\^_`{|}~", "\"%-.<>\\^_`{|}~")]
+        [InlineData("a=%41", "a", "A")] // ascii encoded hex
+        [InlineData("a=%C3%A1", "a", "\u00e1")] // utf8 code points
+        [InlineData("a=%u20AC", "a", "%u20AC")] // utf16 not supported
+        public async Task ReadForm_Decoding(string formData, string key, string expectedValue)
+        {
+            var body = MakeStream(bufferRequest: false, text: formData);
+
+            var form = await ReadFormAsync(new FormReader(body));
+
+            Assert.Equal(expectedValue, form[key]);
+        }
+
+        protected virtual Task<Dictionary<string, StringValues>> ReadFormAsync(FormReader reader)
+        {
+            return Task.FromResult(reader.ReadForm());
+        }
+
+        protected virtual Task<KeyValuePair<string, string>?> ReadPair(FormReader reader)
+        {
+            return Task.FromResult(reader.ReadNextPair());
+        }
+
+        private static Stream MakeStream(bool bufferRequest, string text)
+        {
+            var formContent = Encoding.UTF8.GetBytes(text);
+            Stream body = new MemoryStream(formContent);
+            if (!bufferRequest)
+            {
+                body = new NonSeekableReadStream(body);
+            }
+            return body;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/WebUtilities/test/HttpRequestStreamReaderTest.cs b/src/Http/WebUtilities/test/HttpRequestStreamReaderTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..062342fa4cda7312e52ab2c826672577c6063f94
--- /dev/null
+++ b/src/Http/WebUtilities/test/HttpRequestStreamReaderTest.cs
@@ -0,0 +1,313 @@
+// 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 Moq;
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+
+namespace Microsoft.AspNetCore.WebUtilities.Test
+{
+    public class HttpResponseStreamReaderTest
+    {
+        private static readonly char[] CharData = new char[]
+        {
+            char.MinValue,
+            char.MaxValue,
+            '\t',
+            ' ',
+            '$',
+            '@',
+            '#',
+            '\0',
+            '\v',
+            '\'',
+            '\u3190',
+            '\uC3A0',
+            'A',
+            '5',
+            '\r',
+            '\uFE70',
+            '-',
+            ';',
+            '\r',
+            '\n',
+            'T',
+            '3',
+            '\n',
+            'K',
+            '\u00E6',
+        };
+
+        [Fact]
+        public static async Task ReadToEndAsync()
+        {
+            // Arrange
+            var reader = new HttpRequestStreamReader(GetLargeStream(), Encoding.UTF8);
+
+            var result = await reader.ReadToEndAsync();
+
+            Assert.Equal(5000, result.Length);
+        }
+
+        [Fact]
+        public static void TestRead()
+        {
+            // Arrange
+            var reader = CreateReader();
+
+            // Act & Assert
+            for (var i = 0; i < CharData.Length; i++)
+            {
+                var tmp = reader.Read();
+                Assert.Equal((int)CharData[i], tmp);
+            }
+        }
+
+        [Fact]
+        public static void TestPeek()
+        {
+            // Arrange
+            var reader = CreateReader();
+
+            // Act & Assert
+            for (var i = 0; i < CharData.Length; i++)
+            {
+                var peek = reader.Peek();
+                Assert.Equal((int)CharData[i], peek);
+
+                reader.Read();
+            }
+        }
+
+        [Fact]
+        public static void EmptyStream()
+        {
+            // Arrange
+            var reader = new HttpRequestStreamReader(new MemoryStream(), Encoding.UTF8);
+            var buffer = new char[10];
+
+            // Act
+            var read = reader.Read(buffer, 0, 1);
+
+            // Assert
+            Assert.Equal(0, read);
+        }
+
+        [Fact]
+        public static void Read_ReadAllCharactersAtOnce()
+        {
+            // Arrange
+            var reader = CreateReader();
+            var chars = new char[CharData.Length];
+
+            // Act
+            var read = reader.Read(chars, 0, chars.Length);
+
+            // Assert
+            Assert.Equal(chars.Length, read);
+            for (var i = 0; i < CharData.Length; i++)
+            {
+                Assert.Equal(CharData[i], chars[i]);
+            }
+        }
+
+        [Fact]
+        public static async Task Read_ReadInTwoChunks()
+        {
+            // Arrange
+            var reader = CreateReader();
+            var chars = new char[CharData.Length];
+
+            // Act
+            var read = await reader.ReadAsync(chars, 4, 3);
+
+            // Assert
+            Assert.Equal(3, read);
+            for (var i = 0; i < 3; i++)
+            {
+                Assert.Equal(CharData[i], chars[i + 4]);
+            }
+        }
+
+        [Fact]
+        public static void ReadLine_ReadMultipleLines()
+        {
+            // Arrange
+            var reader = CreateReader();
+            var valueString = new string(CharData);
+
+            // Act & Assert
+            var data = reader.ReadLine();
+            Assert.Equal(valueString.Substring(0, valueString.IndexOf('\r')), data);
+
+            data = reader.ReadLine();
+            Assert.Equal(valueString.Substring(valueString.IndexOf('\r') + 1, 3), data);
+
+            data = reader.ReadLine();
+            Assert.Equal(valueString.Substring(valueString.IndexOf('\n') + 1, 2), data);
+
+            data = reader.ReadLine();
+            Assert.Equal((valueString.Substring(valueString.LastIndexOf('\n') + 1)), data);
+        }
+
+        [Fact]
+        public static void ReadLine_ReadWithNoNewlines()
+        {
+            // Arrange
+            var reader = CreateReader();
+            var valueString = new string(CharData);
+            var temp = new char[10];
+
+            // Act
+            reader.Read(temp, 0, 1);
+            var data = reader.ReadLine();
+
+            // Assert
+            Assert.Equal(valueString.Substring(1, valueString.IndexOf('\r') - 1), data);
+        }
+
+        [Fact]
+        public static async Task ReadLineAsync_MultipleContinuousLines()
+        {
+            // Arrange
+            var stream = new MemoryStream();
+            var writer = new StreamWriter(stream);
+            writer.Write("\n\n\r\r\n");
+            writer.Flush();
+            stream.Position = 0;
+
+            var reader = new HttpRequestStreamReader(stream, Encoding.UTF8);
+
+            // Act & Assert
+            for (var i = 0; i < 4; i++)
+            {
+                var data = await reader.ReadLineAsync();
+                Assert.Equal(string.Empty, data);
+            }
+
+            var eol = await reader.ReadLineAsync();
+            Assert.Null(eol);
+        }
+
+        [Theory]
+        [MemberData(nameof(HttpRequestNullData))]
+        public static void NullInputsInConstructor_ExpectArgumentNullException(Stream stream, Encoding encoding, ArrayPool<byte> bytePool, ArrayPool<char> charPool)
+        {
+            Assert.Throws<ArgumentNullException>(() =>
+            {
+                var httpRequestStreamReader = new HttpRequestStreamReader(stream, encoding, 1, bytePool, charPool);
+            });
+        }
+
+
+
+        [Theory]
+        [InlineData(0)]
+        [InlineData(-1)]
+        public static void NegativeOrZeroBufferSize_ExpectArgumentOutOfRangeException(int size)
+        {
+            Assert.Throws<ArgumentOutOfRangeException>(() =>
+            {
+                var httpRequestStreamReader = new HttpRequestStreamReader(new MemoryStream(), Encoding.UTF8, size, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
+            });
+        }
+
+        [Fact]
+        public static void StreamCannotRead_ExpectArgumentException()
+        {
+            var mockStream = new Mock<Stream>();
+            mockStream.Setup(m => m.CanRead).Returns(false);
+            Assert.Throws<ArgumentException>(() =>
+            {
+                var httpRequestStreamReader = new HttpRequestStreamReader(mockStream.Object, Encoding.UTF8, 1, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
+            });
+        }
+
+        [Theory]
+        [MemberData(nameof(HttpRequestDisposeData))]
+        public static void StreamDisposed_ExpectedObjectDisposedException(Action<HttpRequestStreamReader> action)
+        {
+            var httpRequestStreamReader = new HttpRequestStreamReader(new MemoryStream(), Encoding.UTF8, 10, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
+            httpRequestStreamReader.Dispose();
+
+            Assert.Throws<ObjectDisposedException>(() =>
+            {
+                action(httpRequestStreamReader);
+            });
+        }
+
+        [Fact]
+        public static async Task StreamDisposed_ExpectObjectDisposedExceptionAsync()
+        {
+            var httpRequestStreamReader = new HttpRequestStreamReader(new MemoryStream(), Encoding.UTF8, 10, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
+            httpRequestStreamReader.Dispose();
+
+            await Assert.ThrowsAsync<ObjectDisposedException>(() =>
+            {
+                return httpRequestStreamReader.ReadAsync(new char[10], 0, 1);
+            });
+        }
+        private static HttpRequestStreamReader CreateReader()
+        {
+            var stream = new MemoryStream();
+            var writer = new StreamWriter(stream);
+            writer.Write(CharData);
+            writer.Flush();
+            stream.Position = 0;
+
+            return new HttpRequestStreamReader(stream, Encoding.UTF8);
+        }
+
+        private static MemoryStream GetSmallStream()
+        {
+            var testData = new byte[] { 72, 69, 76, 76, 79 };
+            return new MemoryStream(testData);
+        }
+
+        private static MemoryStream GetLargeStream()
+        {
+            var testData = new byte[] { 72, 69, 76, 76, 79 };
+            // System.Collections.Generic.
+
+            var data = new List<byte>();
+            for (var i = 0; i < 1000; i++)
+            {
+                data.AddRange(testData);
+            }
+
+            return new MemoryStream(data.ToArray());
+        }
+
+        public static IEnumerable<object[]> HttpRequestNullData()
+        {
+            yield return new object[] { null, Encoding.UTF8, ArrayPool<byte>.Shared, ArrayPool<char>.Shared };
+            yield return new object[] { new MemoryStream(), null, ArrayPool<byte>.Shared, ArrayPool<char>.Shared };
+            yield return new object[] { new MemoryStream(), Encoding.UTF8, null, ArrayPool<char>.Shared };
+            yield return new object[] { new MemoryStream(), Encoding.UTF8, ArrayPool<byte>.Shared, null };
+        }
+
+        public static IEnumerable<object[]> HttpRequestDisposeData()
+        {
+            yield return new object[] { new Action<HttpRequestStreamReader>((httpRequestStreamReader) =>
+            {
+                 var res = httpRequestStreamReader.Read();
+            })};
+            yield return new object[] { new Action<HttpRequestStreamReader>((httpRequestStreamReader) =>
+            {
+                 var res = httpRequestStreamReader.Read(new char[10], 0, 1);
+            })};
+
+            yield return new object[] { new Action<HttpRequestStreamReader>((httpRequestStreamReader) =>
+            {
+                var res = httpRequestStreamReader.Peek();
+            })};
+
+        }
+    }
+}
diff --git a/src/Http/WebUtilities/test/HttpResponseStreamWriterTest.cs b/src/Http/WebUtilities/test/HttpResponseStreamWriterTest.cs
new file mode 100644
index 0000000000000000000000000000000000000000..7847e1384e24a8620e0bbaa1bb01ec18e5ef69d8
--- /dev/null
+++ b/src/Http/WebUtilities/test/HttpResponseStreamWriterTest.cs
@@ -0,0 +1,574 @@
+// 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 Moq;
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace Microsoft.AspNetCore.WebUtilities.Test
+{
+    public class HttpResponseStreamWriterTest
+    {
+        private const int DefaultCharacterChunkSize = HttpResponseStreamWriter.DefaultBufferSize;
+
+        [Fact]
+        public async Task DoesNotWriteBOM()
+        {
+            // Arrange
+            var memoryStream = new MemoryStream();
+            var encodingWithBOM = new UTF8Encoding(encoderShouldEmitUTF8Identifier: true);
+            var writer = new HttpResponseStreamWriter(memoryStream, encodingWithBOM);
+            var expectedData = new byte[] { 97, 98, 99, 100 }; // without BOM
+
+            // Act
+            using (writer)
+            {
+                await writer.WriteAsync("abcd");
+            }
+
+            // Assert
+            Assert.Equal(expectedData, memoryStream.ToArray());
+        }
+
+        [Fact]
+        public async Task DoesNotFlush_UnderlyingStream_OnDisposingWriter()
+        {
+            // Arrange
+            var stream = new TestMemoryStream();
+            var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+
+            // Act
+            await writer.WriteAsync("Hello");
+            writer.Dispose();
+
+            // Assert
+            Assert.Equal(0, stream.FlushCallCount);
+            Assert.Equal(0, stream.FlushAsyncCallCount);
+        }
+
+        [Fact]
+        public async Task DoesNotDispose_UnderlyingStream_OnDisposingWriter()
+        {
+            // Arrange
+            var stream = new TestMemoryStream();
+            var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+
+            // Act
+            await writer.WriteAsync("Hello world");
+            writer.Dispose();
+
+            // Assert
+            Assert.Equal(0, stream.DisposeCallCount);
+        }
+
+        [Theory]
+        [InlineData(1023)]
+        [InlineData(1024)]
+        [InlineData(1050)]
+        [InlineData(2048)]
+        public async Task FlushesBuffer_OnClose(int byteLength)
+        {
+            // Arrange
+            var stream = new TestMemoryStream();
+            var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+            await writer.WriteAsync(new string('a', byteLength));
+
+            // Act
+            writer.Dispose();
+
+            // Assert
+            Assert.Equal(0, stream.FlushCallCount);
+            Assert.Equal(0, stream.FlushAsyncCallCount);
+            Assert.Equal(byteLength, stream.Length);
+        }
+
+        [Theory]
+        [InlineData(1023)]
+        [InlineData(1024)]
+        [InlineData(1050)]
+        [InlineData(2048)]
+        public async Task FlushesBuffer_OnDispose(int byteLength)
+        {
+            // Arrange
+            var stream = new TestMemoryStream();
+            var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+            await writer.WriteAsync(new string('a', byteLength));
+
+            // Act
+            writer.Dispose();
+
+            // Assert
+            Assert.Equal(0, stream.FlushCallCount);
+            Assert.Equal(0, stream.FlushAsyncCallCount);
+            Assert.Equal(byteLength, stream.Length);
+        }
+
+        [Fact]
+        public void NoDataWritten_Flush_DoesNotFlushUnderlyingStream()
+        {
+            // Arrange
+            var stream = new TestMemoryStream();
+            var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+
+            // Act
+            writer.Flush();
+
+            // Assert
+            Assert.Equal(0, stream.FlushCallCount);
+            Assert.Equal(0, stream.Length);
+        }
+
+        [Theory]
+        [InlineData(1023)]
+        [InlineData(1024)]
+        [InlineData(1050)]
+        [InlineData(2048)]
+        public void FlushesBuffer_ButNotStream_OnFlush(int byteLength)
+        {
+            // Arrange
+            var stream = new TestMemoryStream();
+            var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+            writer.Write(new string('a', byteLength));
+
+            var expectedWriteCount = Math.Ceiling((double)byteLength / HttpResponseStreamWriter.DefaultBufferSize);
+
+            // Act
+            writer.Flush();
+
+            // Assert
+            Assert.Equal(0, stream.FlushCallCount);
+            Assert.Equal(expectedWriteCount, stream.WriteCallCount);
+            Assert.Equal(byteLength, stream.Length);
+        }
+
+        [Fact]
+        public async Task NoDataWritten_FlushAsync_DoesNotFlushUnderlyingStream()
+        {
+            // Arrange
+            var stream = new TestMemoryStream();
+            var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+
+            // Act
+            await writer.FlushAsync();
+
+            // Assert
+            Assert.Equal(0, stream.FlushAsyncCallCount);
+            Assert.Equal(0, stream.Length);
+        }
+
+        [Theory]
+        [InlineData(HttpResponseStreamWriter.DefaultBufferSize - 1)]
+        [InlineData(HttpResponseStreamWriter.DefaultBufferSize)]
+        [InlineData(HttpResponseStreamWriter.DefaultBufferSize + 1)]
+        [InlineData(HttpResponseStreamWriter.DefaultBufferSize * 2)]
+        public async Task FlushesBuffer_ButNotStream_OnFlushAsync(int byteLength)
+        {
+            // Arrange
+            var stream = new TestMemoryStream();
+            var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+            await writer.WriteAsync(new string('a', byteLength));
+
+            var expectedWriteCount = Math.Ceiling((double)byteLength / HttpResponseStreamWriter.DefaultBufferSize);
+
+            // Act
+            await writer.FlushAsync();
+
+            // Assert
+            Assert.Equal(0, stream.FlushAsyncCallCount);
+            Assert.Equal(expectedWriteCount, stream.WriteAsyncCallCount);
+            Assert.Equal(byteLength, stream.Length);
+        }
+
+        [Theory]
+        [InlineData(1023)]
+        [InlineData(1024)]
+        public async Task FlushWriteThrows_DontFlushInDispose(int byteLength)
+        {
+            // Arrange
+            var stream = new TestMemoryStream() { ThrowOnWrite = true };
+            var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+
+            await writer.WriteAsync(new string('a', byteLength));
+            await Assert.ThrowsAsync<IOException>(() => writer.FlushAsync());
+
+            // Act
+            writer.Dispose();
+
+            // Assert
+            Assert.Equal(1, stream.WriteAsyncCallCount);
+            Assert.Equal(0, stream.WriteCallCount);
+            Assert.Equal(0, stream.FlushCallCount);
+            Assert.Equal(0, stream.FlushAsyncCallCount);
+            Assert.Equal(0, stream.Length);
+        }
+
+        [Theory]
+        [InlineData(1023)]
+        [InlineData(1024)]
+        [InlineData(1050)]
+        [InlineData(2048)]
+        public void WriteChar_WritesToStream(int byteLength)
+        {
+            // Arrange
+            var stream = new TestMemoryStream();
+            var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+
+            // Act
+            using (writer)
+            {
+                for (var i = 0; i < byteLength; i++)
+                {
+                    writer.Write('a');
+                }
+            }
+
+            // Assert
+            Assert.Equal(byteLength, stream.Length);
+        }
+
+        [Theory]
+        [InlineData(1023)]
+        [InlineData(1024)]
+        [InlineData(1050)]
+        [InlineData(2048)]
+        public void WriteCharArray_WritesToStream(int byteLength)
+        {
+            // Arrange
+            var stream = new TestMemoryStream();
+            var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+
+            // Act
+            using (writer)
+            {
+                writer.Write((new string('a', byteLength)).ToCharArray());
+            }
+
+            // Assert
+            Assert.Equal(byteLength, stream.Length);
+        }
+
+        [Theory]
+        [InlineData(1023)]
+        [InlineData(1024)]
+        [InlineData(1050)]
+        [InlineData(2048)]
+        public async Task WriteCharAsync_WritesToStream(int byteLength)
+        {
+            // Arrange
+            var stream = new TestMemoryStream();
+            var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+
+            // Act
+            using (writer)
+            {
+                for (var i = 0; i < byteLength; i++)
+                {
+                    await writer.WriteAsync('a');
+                }
+            }
+
+            // Assert
+            Assert.Equal(byteLength, stream.Length);
+        }
+
+        [Theory]
+        [InlineData(1023)]
+        [InlineData(1024)]
+        [InlineData(1050)]
+        [InlineData(2048)]
+        public async Task WriteCharArrayAsync_WritesToStream(int byteLength)
+        {
+            // Arrange
+            var stream = new TestMemoryStream();
+            var writer = new HttpResponseStreamWriter(stream, Encoding.UTF8);
+
+            // Act
+            using (writer)
+            {
+                await writer.WriteAsync((new string('a', byteLength)).ToCharArray());
+            }
+
+            // Assert
+            Assert.Equal(byteLength, stream.Length);
+        }
+
+        [Theory]
+        [InlineData("你好世界", "utf-16")]
+        [InlineData("హలో ప్రపంచ", "iso-8859-1")]
+        [InlineData("வணக்கம் உலக", "utf-32")]
+        public async Task WritesData_InExpectedEncoding(string data, string encodingName)
+        {
+            // Arrange
+            var encoding = Encoding.GetEncoding(encodingName);
+            var expectedBytes = encoding.GetBytes(data);
+            var stream = new MemoryStream();
+            var writer = new HttpResponseStreamWriter(stream, encoding);
+
+            // Act
+            using (writer)
+            {
+                await writer.WriteAsync(data);
+            }
+
+            // Assert
+            Assert.Equal(expectedBytes, stream.ToArray());
+        }
+
+        [Theory]
+        [InlineData('ã‚“', 1023, "utf-8")]
+        [InlineData('ã‚“', 1024, "utf-8")]
+        [InlineData('ã‚“', 1050, "utf-8")]
+        [InlineData('ä½ ', 1023, "utf-16")]
+        [InlineData('ä½ ', 1024, "utf-16")]
+        [InlineData('ä½ ', 1050, "utf-16")]
+        [InlineData('à°¹', 1023, "iso-8859-1")]
+        [InlineData('à°¹', 1024, "iso-8859-1")]
+        [InlineData('à°¹', 1050, "iso-8859-1")]
+        [InlineData('வ', 1023, "utf-32")]
+        [InlineData('வ', 1024, "utf-32")]
+        [InlineData('வ', 1050, "utf-32")]
+        public async Task WritesData_OfDifferentLength_InExpectedEncoding(
+            char character,
+            int charCount,
+            string encodingName)
+        {
+            // Arrange
+            var encoding = Encoding.GetEncoding(encodingName);
+            string data = new string(character, charCount);
+            var expectedBytes = encoding.GetBytes(data);
+            var stream = new MemoryStream();
+            var writer = new HttpResponseStreamWriter(stream, encoding);
+
+            // Act
+            using (writer)
+            {
+                await writer.WriteAsync(data);
+            }
+
+            // Assert
+            Assert.Equal(expectedBytes, stream.ToArray());
+        }
+
+        // None of the code in HttpResponseStreamWriter differs significantly when using pooled buffers.
+        //
+        // This test effectively verifies that things are correctly constructed and disposed. Pooled buffers
+        // throw on the finalizer thread if not disposed, so that's why it's complicated.
+        [Fact]
+        public void HttpResponseStreamWriter_UsingPooledBuffers()
+        {
+            // Arrange
+            var encoding = Encoding.UTF8;
+            var stream = new MemoryStream();
+
+            var expectedBytes = encoding.GetBytes("Hello, World!");
+
+            using (var writer = new HttpResponseStreamWriter(
+                stream,
+                encoding,
+                1024,
+                ArrayPool<byte>.Shared,
+                ArrayPool<char>.Shared))
+            {
+                // Act
+                writer.Write("Hello, World!");
+            }
+
+            // Assert
+            Assert.Equal(expectedBytes, stream.ToArray());
+        }
+
+        [Theory]
+        [InlineData(DefaultCharacterChunkSize)]
+        [InlineData(DefaultCharacterChunkSize * 2)]
+        [InlineData(DefaultCharacterChunkSize * 3)]
+        public async Task HttpResponseStreamWriter_WritesDataCorrectly_ForCharactersHavingSurrogatePairs(int characterSize)
+        {
+            // Arrange
+            // Here "𐐀" (called Deseret Long I) actually represents 2 characters. Try to make this character split across
+            // the boundary
+            var content = new string('a', characterSize - 1) + "𐐀";
+            var stream = new TestMemoryStream();
+            var writer = new HttpResponseStreamWriter(stream, Encoding.Unicode);
+
+            // Act
+            await writer.WriteAsync(content);
+            await writer.FlushAsync();
+
+            // Assert
+            stream.Seek(0, SeekOrigin.Begin);
+            var streamReader = new StreamReader(stream, Encoding.Unicode);
+            var actualContent = await streamReader.ReadToEndAsync();
+            Assert.Equal(content, actualContent);
+        }
+
+        [Theory]
+        [MemberData(nameof(HttpResponseStreamWriterData))]
+        public static void NullInputsInConstructor_ExpectArgumentNullException(Stream stream, Encoding encoding, ArrayPool<byte> bytePool, ArrayPool<char> charPool)
+        {
+            Assert.Throws<ArgumentNullException>(() =>
+            {
+                var httpRequestStreamReader = new HttpResponseStreamWriter(stream, encoding, 1, bytePool, charPool);
+            });
+        }
+
+        [Theory]
+        [InlineData(0)]
+        [InlineData(-1)]
+        public static void NegativeOrZeroBufferSize_ExpectArgumentOutOfRangeException(int size)
+        {
+            Assert.Throws<ArgumentOutOfRangeException>(() =>
+            {
+                var httpRequestStreamReader = new HttpRequestStreamReader(new MemoryStream(), Encoding.UTF8, size, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
+            });
+        }
+
+        [Fact]
+        public static void StreamCannotRead_ExpectArgumentException()
+        {
+            var mockStream = new Mock<Stream>();
+            mockStream.Setup(m => m.CanWrite).Returns(false);
+            Assert.Throws<ArgumentException>(() =>
+            {
+                var httpRequestStreamReader = new HttpRequestStreamReader(mockStream.Object, Encoding.UTF8, 1, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
+            });
+        }
+
+        [Theory]
+        [MemberData(nameof(HttpResponseDisposeData))]
+        public static void StreamDisposed_ExpectedObjectDisposedException(Action<HttpResponseStreamWriter> action)
+        {
+            var httpResponseStreamWriter = new HttpResponseStreamWriter(new MemoryStream(), Encoding.UTF8, 10, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
+            httpResponseStreamWriter.Dispose();
+
+            Assert.Throws<ObjectDisposedException>(() =>
+            {
+                action(httpResponseStreamWriter);
+            });
+        }
+
+        [Theory]
+        [MemberData(nameof(HttpResponseDisposeDataAsync))]
+        public static async Task StreamDisposed_ExpectedObjectDisposedExceptionAsync(Func<HttpResponseStreamWriter, Task> function)
+        {
+            var httpResponseStreamWriter = new HttpResponseStreamWriter(new MemoryStream(), Encoding.UTF8, 10, ArrayPool<byte>.Shared, ArrayPool<char>.Shared);
+            httpResponseStreamWriter.Dispose();
+
+            await Assert.ThrowsAsync<ObjectDisposedException>(() =>
+            {
+                 return function(httpResponseStreamWriter);
+            });
+        }
+
+
+        private class TestMemoryStream : MemoryStream
+        {
+            public int FlushCallCount { get; private set; }
+
+            public int FlushAsyncCallCount { get; private set; }
+
+            public int CloseCallCount { get; private set; }
+
+            public int DisposeCallCount { get; private set; }
+
+            public int WriteCallCount { get; private set; }
+
+            public int WriteAsyncCallCount { get; private set; }
+
+            public bool ThrowOnWrite { get; set; }
+
+            public override void Flush()
+            {
+                FlushCallCount++;
+                base.Flush();
+            }
+
+            public override Task FlushAsync(CancellationToken cancellationToken)
+            {
+                FlushAsyncCallCount++;
+                return base.FlushAsync(cancellationToken);
+            }
+
+            public override void Write(byte[] buffer, int offset, int count)
+            {
+                WriteCallCount++;
+                if (ThrowOnWrite)
+                {
+                    throw new IOException("Test IOException");
+                }
+                base.Write(buffer, offset, count);
+            }
+
+            public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+            {
+                WriteAsyncCallCount++;
+                if (ThrowOnWrite)
+                {
+                    throw new IOException("Test IOException");
+                }
+                return base.WriteAsync(buffer, offset, count, cancellationToken);
+            }
+
+            protected override void Dispose(bool disposing)
+            {
+                DisposeCallCount++;
+                base.Dispose(disposing);
+            }
+        }
+
+        public static IEnumerable<object[]> HttpResponseStreamWriterData()
+        {
+            yield return new object[] { null, Encoding.UTF8, ArrayPool<byte>.Shared, ArrayPool<char>.Shared };
+            yield return new object[] { new MemoryStream(), null, ArrayPool<byte>.Shared, ArrayPool<char>.Shared };
+            yield return new object[] { new MemoryStream(), Encoding.UTF8, null, ArrayPool<char>.Shared };
+            yield return new object[] { new MemoryStream(), Encoding.UTF8, ArrayPool<byte>.Shared, null };
+        }
+
+        public static IEnumerable<object[]> HttpResponseDisposeData()
+        {
+            yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
+            {
+                 httpResponseStreamWriter.Write('a');
+            })};
+            yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
+            {
+                 httpResponseStreamWriter.Write(new char[] { 'a', 'b' }, 0, 1);
+            })};
+
+            yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
+            {
+                httpResponseStreamWriter.Write("hello");
+            })};
+            yield return new object[] { new Action<HttpResponseStreamWriter>((httpResponseStreamWriter) =>
+            {
+                httpResponseStreamWriter.Flush();
+            })};
+        }
+
+        public static IEnumerable<object[]> HttpResponseDisposeDataAsync()
+        {
+            yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
+            {
+                await httpResponseStreamWriter.WriteAsync('a');
+            })};
+            yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
+            {
+                await httpResponseStreamWriter.WriteAsync(new char[] { 'a', 'b' }, 0, 1);
+            })};
+
+            yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
+            {
+                await httpResponseStreamWriter.WriteAsync("hello");
+            })};
+            yield return new object[] { new Func<HttpResponseStreamWriter, Task>(async (httpResponseStreamWriter) =>
+            {
+                await httpResponseStreamWriter.FlushAsync();
+            })};
+        }
+    }
+}
diff --git a/src/Http/WebUtilities/test/Microsoft.AspNetCore.WebUtilities.Tests.csproj b/src/Http/WebUtilities/test/Microsoft.AspNetCore.WebUtilities.Tests.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..8a91421e65d405abd8c91914ca30d62c2307dba3
--- /dev/null
+++ b/src/Http/WebUtilities/test/Microsoft.AspNetCore.WebUtilities.Tests.csproj
@@ -0,0 +1,11 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>$(StandardTestTfms)</TargetFrameworks>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.WebUtilities" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Http/WebUtilities/test/MultipartReaderTests.cs b/src/Http/WebUtilities/test/MultipartReaderTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..d66ea98fedfaaf05f7ae38f8f914d219c9ab0234
--- /dev/null
+++ b/src/Http/WebUtilities/test/MultipartReaderTests.cs
@@ -0,0 +1,383 @@
+// 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 Xunit;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    public class MultipartReaderTests
+    {
+        private const string Boundary = "9051914041544843365972754266";
+        // Note that CRLF (\r\n) is required. You can't use multi-line C# strings here because the line breaks on Linux are just LF.
+        private const string OnePartBody =
+"--9051914041544843365972754266\r\n" +
+"Content-Disposition: form-data; name=\"text\"\r\n" +
+"\r\n" +
+"text default\r\n" +
+"--9051914041544843365972754266--\r\n";
+        private const string OnePartBodyTwoHeaders =
+"--9051914041544843365972754266\r\n" +
+"Content-Disposition: form-data; name=\"text\"\r\n" +
+"Custom-header: custom-value\r\n" +
+"\r\n" +
+"text default\r\n" +
+"--9051914041544843365972754266--\r\n";
+        private const string OnePartBodyWithTrailingWhitespace =
+"--9051914041544843365972754266             \r\n" +
+"Content-Disposition: form-data; name=\"text\"\r\n" +
+"\r\n" +
+"text default\r\n" +
+"--9051914041544843365972754266--\r\n";
+        // It's non-compliant but common to leave off the last CRLF.
+        private const string OnePartBodyWithoutFinalCRLF =
+"--9051914041544843365972754266\r\n" +
+"Content-Disposition: form-data; name=\"text\"\r\n" +
+"\r\n" +
+"text default\r\n" +
+"--9051914041544843365972754266--";
+        private const string TwoPartBody =
+"--9051914041544843365972754266\r\n" +
+"Content-Disposition: form-data; name=\"text\"\r\n" +
+"\r\n" +
+"text default\r\n" +
+"--9051914041544843365972754266\r\n" +
+"Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\r\n" +
+"Content-Type: text/plain\r\n" +
+"\r\n" +
+"Content of a.txt.\r\n" +
+"\r\n" +
+"--9051914041544843365972754266--\r\n";
+        private const string TwoPartBodyWithUnicodeFileName =
+"--9051914041544843365972754266\r\n" +
+"Content-Disposition: form-data; name=\"text\"\r\n" +
+"\r\n" +
+"text default\r\n" +
+"--9051914041544843365972754266\r\n" +
+"Content-Disposition: form-data; name=\"file1\"; filename=\"a色.txt\"\r\n" +
+"Content-Type: text/plain\r\n" +
+"\r\n" +
+"Content of a.txt.\r\n" +
+"\r\n" +
+"--9051914041544843365972754266--\r\n";
+        private const string ThreePartBody =
+"--9051914041544843365972754266\r\n" +
+"Content-Disposition: form-data; name=\"text\"\r\n" +
+"\r\n" +
+"text default\r\n" +
+"--9051914041544843365972754266\r\n" +
+"Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\r\n" +
+"Content-Type: text/plain\r\n" +
+"\r\n" +
+"Content of a.txt.\r\n" +
+"\r\n" +
+"--9051914041544843365972754266\r\n" +
+"Content-Disposition: form-data; name=\"file2\"; filename=\"a.html\"\r\n" +
+"Content-Type: text/html\r\n" +
+"\r\n" +
+"<!DOCTYPE html><title>Content of a.html.</title>\r\n" +
+"\r\n" +
+"--9051914041544843365972754266--\r\n";
+
+        private const string TwoPartBodyIncompleteBuffer =
+"--9051914041544843365972754266\r\n" +
+"Content-Disposition: form-data; name=\"text\"\r\n" +
+"\r\n" +
+"text default\r\n" +
+"--9051914041544843365972754266\r\n" +
+"Content-Disposition: form-data; name=\"file1\"; filename=\"a.txt\"\r\n" +
+"Content-Type: text/plain\r\n" +
+"\r\n" +
+"Content of a.txt.\r\n" +
+"\r\n" +
+"--9051914041544843365";
+
+        private static MemoryStream MakeStream(string text)
+        {
+            return new MemoryStream(Encoding.UTF8.GetBytes(text));
+        }
+
+        private static string GetString(byte[] buffer, int count)
+        {
+            return Encoding.ASCII.GetString(buffer, 0, count);
+        }
+
+        [Fact]
+        public async Task MutipartReader_ReadSinglePartBody_Success()
+        {
+            var stream = MakeStream(OnePartBody);
+            var reader = new MultipartReader(Boundary, stream);
+
+            var section = await reader.ReadNextSectionAsync();
+            Assert.NotNull(section);
+            Assert.Single(section.Headers);
+            Assert.Equal("form-data; name=\"text\"", section.Headers["Content-Disposition"][0]);
+            var buffer = new MemoryStream();
+            await section.Body.CopyToAsync(buffer);
+            Assert.Equal("text default", Encoding.ASCII.GetString(buffer.ToArray()));
+
+            Assert.Null(await reader.ReadNextSectionAsync());
+        }
+
+        [Fact]
+        public async Task MutipartReader_HeaderCountExceeded_Throws()
+        {
+            var stream = MakeStream(OnePartBodyTwoHeaders);
+            var reader = new MultipartReader(Boundary, stream)
+            {
+                HeadersCountLimit = 1,
+            };
+
+            var exception = await Assert.ThrowsAsync<InvalidDataException>(() => reader.ReadNextSectionAsync());
+            Assert.Equal("Multipart headers count limit 1 exceeded.", exception.Message);
+        }
+
+        [Fact]
+        public async Task MutipartReader_HeadersLengthExceeded_Throws()
+        {
+            var stream = MakeStream(OnePartBodyTwoHeaders);
+            var reader = new MultipartReader(Boundary, stream)
+            {
+                HeadersLengthLimit = 60,
+            };
+
+            var exception = await Assert.ThrowsAsync<InvalidDataException>(() => reader.ReadNextSectionAsync());
+            Assert.Equal("Line length limit 17 exceeded.", exception.Message);
+        }
+
+        [Fact]
+        public async Task MutipartReader_ReadSinglePartBodyWithTrailingWhitespace_Success()
+        {
+            var stream = MakeStream(OnePartBodyWithTrailingWhitespace);
+            var reader = new MultipartReader(Boundary, stream);
+
+            var section = await reader.ReadNextSectionAsync();
+            Assert.NotNull(section);
+            Assert.Single(section.Headers);
+            Assert.Equal("form-data; name=\"text\"", section.Headers["Content-Disposition"][0]);
+            var buffer = new MemoryStream();
+            await section.Body.CopyToAsync(buffer);
+            Assert.Equal("text default", Encoding.ASCII.GetString(buffer.ToArray()));
+
+            Assert.Null(await reader.ReadNextSectionAsync());
+        }
+
+        [Fact]
+        public async Task MutipartReader_ReadSinglePartBodyWithoutLastCRLF_Success()
+        {
+            var stream = MakeStream(OnePartBodyWithoutFinalCRLF);
+            var reader = new MultipartReader(Boundary, stream);
+
+            var section = await reader.ReadNextSectionAsync();
+            Assert.NotNull(section);
+            Assert.Single(section.Headers);
+            Assert.Equal("form-data; name=\"text\"", section.Headers["Content-Disposition"][0]);
+            var buffer = new MemoryStream();
+            await section.Body.CopyToAsync(buffer);
+            Assert.Equal("text default", Encoding.ASCII.GetString(buffer.ToArray()));
+
+            Assert.Null(await reader.ReadNextSectionAsync());
+        }
+
+        [Fact]
+        public async Task MutipartReader_ReadTwoPartBody_Success()
+        {
+            var stream = MakeStream(TwoPartBody);
+            var reader = new MultipartReader(Boundary, stream);
+
+            var section = await reader.ReadNextSectionAsync();
+            Assert.NotNull(section);
+            Assert.Single(section.Headers);
+            Assert.Equal("form-data; name=\"text\"", section.Headers["Content-Disposition"][0]);
+            var buffer = new MemoryStream();
+            await section.Body.CopyToAsync(buffer);
+            Assert.Equal("text default", Encoding.ASCII.GetString(buffer.ToArray()));
+
+            section = await reader.ReadNextSectionAsync();
+            Assert.NotNull(section);
+            Assert.Equal(2, section.Headers.Count);
+            Assert.Equal("form-data; name=\"file1\"; filename=\"a.txt\"", section.Headers["Content-Disposition"][0]);
+            Assert.Equal("text/plain", section.Headers["Content-Type"][0]);
+            buffer = new MemoryStream();
+            await section.Body.CopyToAsync(buffer);
+            Assert.Equal("Content of a.txt.\r\n", Encoding.ASCII.GetString(buffer.ToArray()));
+
+            Assert.Null(await reader.ReadNextSectionAsync());
+        }
+
+        [Fact]
+        public async Task MutipartReader_ReadTwoPartBodyWithUnicodeFileName_Success()
+        {
+            var stream = MakeStream(TwoPartBodyWithUnicodeFileName);
+            var reader = new MultipartReader(Boundary, stream);
+
+            var section = await reader.ReadNextSectionAsync();
+            Assert.NotNull(section);
+            Assert.Single(section.Headers);
+            Assert.Equal("form-data; name=\"text\"", section.Headers["Content-Disposition"][0]);
+            var buffer = new MemoryStream();
+            await section.Body.CopyToAsync(buffer);
+            Assert.Equal("text default", Encoding.ASCII.GetString(buffer.ToArray()));
+
+            section = await reader.ReadNextSectionAsync();
+            Assert.NotNull(section);
+            Assert.Equal(2, section.Headers.Count);
+            Assert.Equal("form-data; name=\"file1\"; filename=\"a色.txt\"", section.Headers["Content-Disposition"][0]);
+            Assert.Equal("text/plain", section.Headers["Content-Type"][0]);
+            buffer = new MemoryStream();
+            await section.Body.CopyToAsync(buffer);
+            Assert.Equal("Content of a.txt.\r\n", Encoding.ASCII.GetString(buffer.ToArray()));
+
+            Assert.Null(await reader.ReadNextSectionAsync());
+        }
+
+        [Fact]
+        public async Task MutipartReader_ThreePartBody_Success()
+        {
+            var stream = MakeStream(ThreePartBody);
+            var reader = new MultipartReader(Boundary, stream);
+
+            var section = await reader.ReadNextSectionAsync();
+            Assert.NotNull(section);
+            Assert.Single(section.Headers);
+            Assert.Equal("form-data; name=\"text\"", section.Headers["Content-Disposition"][0]);
+            var buffer = new MemoryStream();
+            await section.Body.CopyToAsync(buffer);
+            Assert.Equal("text default", Encoding.ASCII.GetString(buffer.ToArray()));
+
+            section = await reader.ReadNextSectionAsync();
+            Assert.NotNull(section);
+            Assert.Equal(2, section.Headers.Count);
+            Assert.Equal("form-data; name=\"file1\"; filename=\"a.txt\"", section.Headers["Content-Disposition"][0]);
+            Assert.Equal("text/plain", section.Headers["Content-Type"][0]);
+            buffer = new MemoryStream();
+            await section.Body.CopyToAsync(buffer);
+            Assert.Equal("Content of a.txt.\r\n", Encoding.ASCII.GetString(buffer.ToArray()));
+
+            section = await reader.ReadNextSectionAsync();
+            Assert.NotNull(section);
+            Assert.Equal(2, section.Headers.Count);
+            Assert.Equal("form-data; name=\"file2\"; filename=\"a.html\"", section.Headers["Content-Disposition"][0]);
+            Assert.Equal("text/html", section.Headers["Content-Type"][0]);
+            buffer = new MemoryStream();
+            await section.Body.CopyToAsync(buffer);
+            Assert.Equal("<!DOCTYPE html><title>Content of a.html.</title>\r\n", Encoding.ASCII.GetString(buffer.ToArray()));
+
+            Assert.Null(await reader.ReadNextSectionAsync());
+        }
+
+        [Fact]
+        public void MutipartReader_BufferSizeMustBeLargerThanBoundary_Throws()
+        {
+            var stream = MakeStream(ThreePartBody);
+            Assert.Throws<ArgumentOutOfRangeException>(() =>
+            {
+                var reader = new MultipartReader(Boundary, stream, 5);
+            });
+        }
+
+        [Fact]
+        public async Task MutipartReader_TwoPartBodyIncompleteBuffer_TwoSectionsReadSuccessfullyThirdSectionThrows()
+        {
+            var stream = MakeStream(TwoPartBodyIncompleteBuffer);
+            var reader = new MultipartReader(Boundary, stream);
+            var buffer = new byte[128];
+
+            //first section can be read successfully
+            var section = await reader.ReadNextSectionAsync();
+            Assert.NotNull(section);
+            Assert.Single(section.Headers);
+            Assert.Equal("form-data; name=\"text\"", section.Headers["Content-Disposition"][0]);
+            var read = section.Body.Read(buffer, 0, buffer.Length);
+            Assert.Equal("text default", GetString(buffer, read));
+
+            //second section can be read successfully (even though the bottom boundary is truncated)
+            section = await reader.ReadNextSectionAsync();
+            Assert.NotNull(section);
+            Assert.Equal(2, section.Headers.Count);
+            Assert.Equal("form-data; name=\"file1\"; filename=\"a.txt\"", section.Headers["Content-Disposition"][0]);
+            Assert.Equal("text/plain", section.Headers["Content-Type"][0]);
+            read = section.Body.Read(buffer, 0, buffer.Length);
+            Assert.Equal("Content of a.txt.\r\n", GetString(buffer, read));
+
+            await Assert.ThrowsAsync<IOException>(async () =>
+            {
+                // we'll be unable to ensure enough bytes are buffered to even contain a final boundary
+                section = await reader.ReadNextSectionAsync();
+            });
+        }
+
+        [Fact]
+        public async Task MutipartReader_ReadInvalidUtf8Header_ReplacementCharacters()
+        {
+            var body1 =
+"--9051914041544843365972754266\r\n" +
+"Content-Disposition: form-data; name=\"text\" filename=\"a";
+
+            var body2 =
+".txt\"\r\n" +
+"\r\n" +
+"text default\r\n" +
+"--9051914041544843365972754266--\r\n";
+            var stream = new MemoryStream();
+            var bytes = Encoding.UTF8.GetBytes(body1);
+            stream.Write(bytes, 0, bytes.Length);
+
+            // Write an invalid utf-8 segment in the middle
+            stream.Write(new byte[] { 0xC1, 0x21 }, 0, 2);
+
+            bytes = Encoding.UTF8.GetBytes(body2);
+            stream.Write(bytes, 0, bytes.Length);
+            stream.Seek(0, SeekOrigin.Begin);
+            var reader = new MultipartReader(Boundary, stream);
+
+            var section = await reader.ReadNextSectionAsync();
+            Assert.NotNull(section);
+            Assert.Single(section.Headers);
+            Assert.Equal("form-data; name=\"text\" filename=\"a\uFFFD!.txt\"", section.Headers["Content-Disposition"][0]);
+            var buffer = new MemoryStream();
+            await section.Body.CopyToAsync(buffer);
+            Assert.Equal("text default", Encoding.ASCII.GetString(buffer.ToArray()));
+
+            Assert.Null(await reader.ReadNextSectionAsync());
+        }
+
+        [Fact]
+        public async Task MutipartReader_ReadInvalidUtf8SurrogateHeader_ReplacementCharacters()
+        {
+            var body1 =
+"--9051914041544843365972754266\r\n" +
+"Content-Disposition: form-data; name=\"text\" filename=\"a";
+
+            var body2 =
+".txt\"\r\n" +
+"\r\n" +
+"text default\r\n" +
+"--9051914041544843365972754266--\r\n";
+            var stream = new MemoryStream();
+            var bytes = Encoding.UTF8.GetBytes(body1);
+            stream.Write(bytes, 0, bytes.Length);
+
+            // Write an invalid utf-8 segment in the middle
+            stream.Write(new byte[] { 0xED, 0xA0, 85 }, 0, 3);
+
+            bytes = Encoding.UTF8.GetBytes(body2);
+            stream.Write(bytes, 0, bytes.Length);
+            stream.Seek(0, SeekOrigin.Begin);
+            var reader = new MultipartReader(Boundary, stream);
+
+            var section = await reader.ReadNextSectionAsync();
+            Assert.NotNull(section);
+            Assert.Single(section.Headers);
+            Assert.Equal("form-data; name=\"text\" filename=\"a\uFFFDU.txt\"", section.Headers["Content-Disposition"][0]);
+            var buffer = new MemoryStream();
+            await section.Body.CopyToAsync(buffer);
+            Assert.Equal("text default", Encoding.ASCII.GetString(buffer.ToArray()));
+
+            Assert.Null(await reader.ReadNextSectionAsync());
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/WebUtilities/test/NonSeekableReadStream.cs b/src/Http/WebUtilities/test/NonSeekableReadStream.cs
new file mode 100644
index 0000000000000000000000000000000000000000..f3c77abb38529d82bda6796b6cb0e98ebfb1c81c
--- /dev/null
+++ b/src/Http/WebUtilities/test/NonSeekableReadStream.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;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    public class NonSeekableReadStream : Stream
+    {
+        private Stream _inner;
+
+        public NonSeekableReadStream(byte[] data)
+            : this(new MemoryStream(data))
+        {
+        }
+
+        public NonSeekableReadStream(Stream inner)
+        {
+            _inner = inner;
+        }
+
+        public override bool CanRead => _inner.CanRead;
+
+        public override bool CanSeek => false;
+
+        public override bool CanWrite => false;
+
+        public override long Length
+        {
+            get { throw new NotSupportedException(); }
+        }
+
+        public override long Position
+        {
+            get { throw new NotSupportedException(); }
+            set { throw new NotSupportedException(); }
+        }
+
+        public override void Flush()
+        {
+            throw new NotImplementedException();
+        }
+
+        public override long Seek(long offset, SeekOrigin origin)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override void SetLength(long value)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override void Write(byte[] buffer, int offset, int count)
+        {
+            throw new NotSupportedException();
+        }
+
+        public override int Read(byte[] buffer, int offset, int count)
+        {
+            count = Math.Max(count, 1);
+            return _inner.Read(buffer, offset, count);
+        }
+
+        public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
+        {
+            count = Math.Max(count, 1);
+            return _inner.ReadAsync(buffer, offset, count, cancellationToken);
+        }
+    }
+}
diff --git a/src/Http/WebUtilities/test/QueryHelpersTests.cs b/src/Http/WebUtilities/test/QueryHelpersTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..5607ab87aa24014c3dad03c500c45f129fab74ef
--- /dev/null
+++ b/src/Http/WebUtilities/test/QueryHelpersTests.cs
@@ -0,0 +1,114 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    public class QueryHelperTests
+    {
+        [Fact]
+        public void ParseQueryWithUniqueKeysWorks()
+        {
+            var collection = QueryHelpers.ParseQuery("?key1=value1&key2=value2");
+            Assert.Equal(2, collection.Count);
+            Assert.Equal("value1", collection["key1"].FirstOrDefault());
+            Assert.Equal("value2", collection["key2"].FirstOrDefault());
+        }
+
+        [Fact]
+        public void ParseQueryWithoutQuestionmarkWorks()
+        {
+            var collection = QueryHelpers.ParseQuery("key1=value1&key2=value2");
+            Assert.Equal(2, collection.Count);
+            Assert.Equal("value1", collection["key1"].FirstOrDefault());
+            Assert.Equal("value2", collection["key2"].FirstOrDefault());
+        }
+
+        [Fact]
+        public void ParseQueryWithDuplicateKeysGroups()
+        {
+            var collection = QueryHelpers.ParseQuery("?key1=valueA&key2=valueB&key1=valueC");
+            Assert.Equal(2, collection.Count);
+            Assert.Equal(new[] { "valueA", "valueC" }, collection["key1"]);
+            Assert.Equal("valueB", collection["key2"].FirstOrDefault());
+        }
+
+        [Fact]
+        public void ParseQueryWithEmptyValuesWorks()
+        {
+            var collection = QueryHelpers.ParseQuery("?key1=&key2=");
+            Assert.Equal(2, collection.Count);
+            Assert.Equal(string.Empty, collection["key1"].FirstOrDefault());
+            Assert.Equal(string.Empty, collection["key2"].FirstOrDefault());
+        }
+
+        [Fact]
+        public void ParseQueryWithEmptyKeyWorks()
+        {
+            var collection = QueryHelpers.ParseQuery("?=value1&=");
+            Assert.Single(collection);
+            Assert.Equal(new[] { "value1", "" }, collection[""]);
+        }
+
+        [Theory]
+        [InlineData("http://contoso.com/", "http://contoso.com/?hello=world")]
+        [InlineData("http://contoso.com/someaction", "http://contoso.com/someaction?hello=world")]
+        [InlineData("http://contoso.com/someaction?q=test", "http://contoso.com/someaction?q=test&hello=world")]
+        [InlineData(
+            "http://contoso.com/someaction?q=test#anchor",
+            "http://contoso.com/someaction?q=test&hello=world#anchor")]
+        [InlineData("http://contoso.com/someaction#anchor", "http://contoso.com/someaction?hello=world#anchor")]
+        [InlineData("http://contoso.com/#anchor", "http://contoso.com/?hello=world#anchor")]
+        [InlineData(
+            "http://contoso.com/someaction?q=test#anchor?value",
+            "http://contoso.com/someaction?q=test&hello=world#anchor?value")]
+        [InlineData(
+            "http://contoso.com/someaction#anchor?stuff",
+            "http://contoso.com/someaction?hello=world#anchor?stuff")]
+        [InlineData(
+            "http://contoso.com/someaction?name?something",
+            "http://contoso.com/someaction?name?something&hello=world")]
+        [InlineData(
+            "http://contoso.com/someaction#name#something",
+            "http://contoso.com/someaction?hello=world#name#something")]
+        public void AddQueryStringWithKeyAndValue(string uri, string expectedUri)
+        {
+            var result = QueryHelpers.AddQueryString(uri, "hello", "world");
+            Assert.Equal(expectedUri, result);
+        }
+
+        [Theory]
+        [InlineData("http://contoso.com/", "http://contoso.com/?hello=world&some=text")]
+        [InlineData("http://contoso.com/someaction", "http://contoso.com/someaction?hello=world&some=text")]
+        [InlineData("http://contoso.com/someaction?q=1", "http://contoso.com/someaction?q=1&hello=world&some=text")]
+        [InlineData("http://contoso.com/some#action", "http://contoso.com/some?hello=world&some=text#action")]
+        [InlineData("http://contoso.com/some?q=1#action", "http://contoso.com/some?q=1&hello=world&some=text#action")]
+        [InlineData("http://contoso.com/#action", "http://contoso.com/?hello=world&some=text#action")]
+        [InlineData(
+            "http://contoso.com/someaction?q=test#anchor?value",
+            "http://contoso.com/someaction?q=test&hello=world&some=text#anchor?value")]
+        [InlineData(
+            "http://contoso.com/someaction#anchor?stuff",
+            "http://contoso.com/someaction?hello=world&some=text#anchor?stuff")]
+        [InlineData(
+            "http://contoso.com/someaction?name?something",
+            "http://contoso.com/someaction?name?something&hello=world&some=text")]
+        [InlineData(
+            "http://contoso.com/someaction#name#something",
+            "http://contoso.com/someaction?hello=world&some=text#name#something")]
+        public void AddQueryStringWithDictionary(string uri, string expectedUri)
+        {
+            var queryStrings = new Dictionary<string, string>()
+                        {
+                            { "hello", "world" },
+                            { "some", "text" }
+                        };
+
+            var result = QueryHelpers.AddQueryString(uri, queryStrings);
+            Assert.Equal(expectedUri, result);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/WebUtilities/test/WebEncodersTests.cs b/src/Http/WebUtilities/test/WebEncodersTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..bb7f71248f58c68809c3a30e21a7a64c5f8202bd
--- /dev/null
+++ b/src/Http/WebUtilities/test/WebEncodersTests.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 Xunit;
+
+namespace Microsoft.AspNetCore.WebUtilities
+{
+    public class WebEncodersTests
+    {
+
+        [Theory]
+        [InlineData("", 1, 0)]
+        [InlineData("", 0, 1)]
+        [InlineData("0123456789", 9, 2)]
+        [InlineData("0123456789", Int32.MaxValue, 2)]
+        [InlineData("0123456789", 9, -1)]
+        public void Base64UrlDecode_BadOffsets(string input, int offset, int count)
+        {
+            // Act & assert
+            Assert.ThrowsAny<ArgumentException>(() =>
+            {
+                var retVal = WebEncoders.Base64UrlDecode(input, offset, count);
+            });
+        }
+
+        [Theory]
+        [InlineData(0, 1, 0)]
+        [InlineData(0, 0, 1)]
+        [InlineData(10, 9, 2)]
+        [InlineData(10, Int32.MaxValue, 2)]
+        [InlineData(10, 9, -1)]
+        public void Base64UrlEncode_BadOffsets(int inputLength, int offset, int count)
+        {
+            // Arrange
+            byte[] input = new byte[inputLength];
+
+            // Act & assert
+            Assert.ThrowsAny<ArgumentException>(() =>
+            {
+                var retVal = WebEncoders.Base64UrlEncode(input, offset, count);
+            });
+        }
+
+        [Fact]
+        public void DataOfVariousLengthRoundTripCorrectly()
+        {
+            for (int length = 0; length != 256; ++length)
+            {
+                var data = new byte[length];
+                for (int index = 0; index != length; ++index)
+                {
+                    data[index] = (byte)(5 + length + (index * 23));
+                }
+                string text = WebEncoders.Base64UrlEncode(data);
+                byte[] result = WebEncoders.Base64UrlDecode(text);
+
+                for (int index = 0; index != length; ++index)
+                {
+                    Assert.Equal(data[index], result[index]);
+                }
+            }
+        }
+    }
+}
diff --git a/src/Http/samples/SampleApp/PooledHttpContext.cs b/src/Http/samples/SampleApp/PooledHttpContext.cs
new file mode 100644
index 0000000000000000000000000000000000000000..58166bb572aef3da4592bee3b0dda7ec956973a9
--- /dev/null
+++ b/src/Http/samples/SampleApp/PooledHttpContext.cs
@@ -0,0 +1,54 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Http.Internal;
+
+namespace SampleApp
+{
+    public class PooledHttpContext : DefaultHttpContext
+    {
+        DefaultHttpRequest _pooledHttpRequest;
+        DefaultHttpResponse _pooledHttpResponse;
+
+        public PooledHttpContext(IFeatureCollection featureCollection) :
+            base(featureCollection)
+        {
+        }
+
+        protected override HttpRequest InitializeHttpRequest()
+        {
+            if (_pooledHttpRequest != null)
+            {
+                _pooledHttpRequest.Initialize(this);
+                return _pooledHttpRequest;
+            }
+
+            return new DefaultHttpRequest(this);
+        }
+
+        protected override void UninitializeHttpRequest(HttpRequest instance)
+        {
+            _pooledHttpRequest = instance as DefaultHttpRequest;
+            _pooledHttpRequest?.Uninitialize();
+        }
+
+        protected override HttpResponse InitializeHttpResponse()
+        {
+            if (_pooledHttpResponse != null)
+            {
+                _pooledHttpResponse.Initialize(this);
+                return _pooledHttpResponse;
+            }
+
+            return new DefaultHttpResponse(this);
+        }
+
+        protected override void UninitializeHttpResponse(HttpResponse instance)
+        {
+            _pooledHttpResponse = instance as DefaultHttpResponse;
+            _pooledHttpResponse?.Uninitialize();
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/Http/samples/SampleApp/PooledHttpContextFactory.cs b/src/Http/samples/SampleApp/PooledHttpContextFactory.cs
new file mode 100644
index 0000000000000000000000000000000000000000..c61e139ac3af1b79151a461d0d374f32591ec457
--- /dev/null
+++ b/src/Http/samples/SampleApp/PooledHttpContextFactory.cs
@@ -0,0 +1,83 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.Extensions.ObjectPool;
+
+namespace SampleApp
+{
+    public class PooledHttpContextFactory : IHttpContextFactory
+    {
+        private readonly IHttpContextAccessor _httpContextAccessor;
+        private readonly Stack<PooledHttpContext> _pool = new Stack<PooledHttpContext>();
+
+        public PooledHttpContextFactory(ObjectPoolProvider poolProvider)
+            : this(poolProvider, httpContextAccessor: null)
+        {
+        }
+
+        public PooledHttpContextFactory(ObjectPoolProvider poolProvider, IHttpContextAccessor httpContextAccessor)
+        {
+            if (poolProvider == null)
+            {
+                throw new ArgumentNullException(nameof(poolProvider));
+            }
+
+            _httpContextAccessor = httpContextAccessor;
+        }
+
+        public HttpContext Create(IFeatureCollection featureCollection)
+        {
+            if (featureCollection == null)
+            {
+                throw new ArgumentNullException(nameof(featureCollection));
+            }
+
+            PooledHttpContext httpContext = null;
+            lock (_pool)
+            {
+                if (_pool.Count != 0)
+                {
+                    httpContext = _pool.Pop();
+                }
+            }
+
+            if (httpContext == null)
+            {
+                httpContext = new PooledHttpContext(featureCollection);
+            }
+            else
+            {
+                httpContext.Initialize(featureCollection);
+            }
+
+            if (_httpContextAccessor != null)
+            {
+                _httpContextAccessor.HttpContext = httpContext;
+            }
+            return httpContext;
+        }
+
+        public void Dispose(HttpContext httpContext)
+        {
+            if (_httpContextAccessor != null)
+            {
+                _httpContextAccessor.HttpContext = null;
+            }
+
+            var pooled = httpContext as PooledHttpContext;
+            if (pooled != null)
+            {
+                pooled.Uninitialize();
+                lock (_pool)
+                {
+                    _pool.Push(pooled);
+                }
+            }
+        }
+    }
+}
diff --git a/src/Http/samples/SampleApp/Program.cs b/src/Http/samples/SampleApp/Program.cs
new file mode 100644
index 0000000000000000000000000000000000000000..28d24befe016e67f2b4e98ee26704394ee082a6b
--- /dev/null
+++ b/src/Http/samples/SampleApp/Program.cs
@@ -0,0 +1,21 @@
+using System;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Extensions;
+
+namespace SampleApp
+{
+    public class Program
+    {
+        public static void Main(string[] args)
+        {
+            var query = new QueryBuilder()
+            {
+                { "hello", "world" }
+            }.ToQueryString();
+
+            var uri = UriHelper.BuildAbsolute("http", new HostString("contoso.com"), query: query);
+
+            Console.WriteLine(uri);
+        }
+    }
+}
diff --git a/src/Http/samples/SampleApp/SampleApp.csproj b/src/Http/samples/SampleApp/SampleApp.csproj
new file mode 100644
index 0000000000000000000000000000000000000000..aedd176becb5ceb9b3dea74e581f2cf1ecc899e0
--- /dev/null
+++ b/src/Http/samples/SampleApp/SampleApp.csproj
@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFrameworks>netcoreapp2.1;net461</TargetFrameworks>
+    <OutputType>Exe</OutputType>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Reference Include="Microsoft.AspNetCore.Http" />
+    <Reference Include="Microsoft.AspNetCore.Http.Extensions" />
+  </ItemGroup>
+
+</Project>
diff --git a/src/Shared/Hosting.WebHostBuilderFactory/FactoryResolutionResult.cs b/src/Shared/Hosting.WebHostBuilderFactory/FactoryResolutionResult.cs
new file mode 100644
index 0000000000000000000000000000000000000000..745cca7ebd44d3cebf457d9dbbcb0040f9082aba
--- /dev/null
+++ b/src/Shared/Hosting.WebHostBuilderFactory/FactoryResolutionResult.cs
@@ -0,0 +1,54 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Hosting.WebHostBuilderFactory
+{
+    internal class FactoryResolutionResult<TWebHost,TWebHostBuilder>
+    {
+        public FactoryResolutionResultKind ResultKind { get; set; }
+        public Type ProgramType { get; set; }
+        public Func<string[], TWebHost> WebHostFactory { get; set; }
+        public Func<string[], TWebHostBuilder> WebHostBuilderFactory { get; set; }
+
+        internal static FactoryResolutionResult<TWebHost, TWebHostBuilder> NoBuildWebHost(Type programType) =>
+            new FactoryResolutionResult<TWebHost, TWebHostBuilder>
+            {
+                ProgramType = programType,
+                ResultKind = FactoryResolutionResultKind.NoBuildWebHost
+            };
+
+        internal static FactoryResolutionResult<TWebHost, TWebHostBuilder> NoCreateWebHostBuilder(Type programType) =>
+            new FactoryResolutionResult<TWebHost, TWebHostBuilder>
+            {
+                ProgramType = programType,
+                ResultKind = FactoryResolutionResultKind.NoCreateWebHostBuilder
+            };
+
+        internal static FactoryResolutionResult<TWebHost, TWebHostBuilder> NoEntryPoint() =>
+            new FactoryResolutionResult<TWebHost, TWebHostBuilder>
+            {
+                ResultKind = FactoryResolutionResultKind.NoEntryPoint
+            };
+
+        internal static FactoryResolutionResult<TWebHost, TWebHostBuilder> Succeded(Func<string[], TWebHost> factory, Type programType) => new FactoryResolutionResult<TWebHost, TWebHostBuilder>
+        {
+            ProgramType = programType,
+            ResultKind = FactoryResolutionResultKind.Success,
+            WebHostFactory = factory
+        };
+
+        internal static FactoryResolutionResult<TWebHost, TWebHostBuilder> Succeded(Func<string[], TWebHostBuilder> factory, Type programType) => new FactoryResolutionResult<TWebHost, TWebHostBuilder>
+        {
+            ProgramType = programType,
+            ResultKind = FactoryResolutionResultKind.Success,
+            WebHostBuilderFactory = factory,
+            WebHostFactory = args =>
+            {
+                var builder = factory(args);
+                return (TWebHost)builder.GetType().GetMethod("Build").Invoke(builder, Array.Empty<object>());
+            }
+        };
+    }
+}
diff --git a/src/Shared/Hosting.WebHostBuilderFactory/FactoryResolutionResultKind.cs b/src/Shared/Hosting.WebHostBuilderFactory/FactoryResolutionResultKind.cs
new file mode 100644
index 0000000000000000000000000000000000000000..bb970d21a491125f393166f0c13012c7cf1755ca
--- /dev/null
+++ b/src/Shared/Hosting.WebHostBuilderFactory/FactoryResolutionResultKind.cs
@@ -0,0 +1,14 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+
+namespace Microsoft.AspNetCore.Hosting.WebHostBuilderFactory
+{
+    internal enum FactoryResolutionResultKind
+    {
+        Success,
+        NoEntryPoint,
+        NoCreateWebHostBuilder,
+        NoBuildWebHost
+    }
+}
diff --git a/src/Shared/Hosting.WebHostBuilderFactory/WebHostFactoryResolver.cs b/src/Shared/Hosting.WebHostBuilderFactory/WebHostFactoryResolver.cs
new file mode 100644
index 0000000000000000000000000000000000000000..916b15e020a4fdfb34c988f9e0417a9df7e83044
--- /dev/null
+++ b/src/Shared/Hosting.WebHostBuilderFactory/WebHostFactoryResolver.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 System.Reflection;
+
+namespace Microsoft.AspNetCore.Hosting.WebHostBuilderFactory
+{
+    internal class WebHostFactoryResolver
+    {
+        public static readonly string CreateWebHostBuilder = nameof(CreateWebHostBuilder);
+        public static readonly string BuildWebHost = nameof(BuildWebHost);
+
+        public static FactoryResolutionResult<TWebhost,TWebhostBuilder> ResolveWebHostBuilderFactory<TWebhost, TWebhostBuilder>(Assembly assembly)
+        {
+            var programType = assembly?.EntryPoint?.DeclaringType;
+            if (programType == null)
+            {
+                return FactoryResolutionResult<TWebhost, TWebhostBuilder>.NoEntryPoint();
+            }
+
+            var factory = programType.GetTypeInfo().GetDeclaredMethod(CreateWebHostBuilder);
+            if (factory == null || 
+                !typeof(TWebhostBuilder).IsAssignableFrom(factory.ReturnType) ||
+                factory.GetParameters().Length != 1 ||
+                !typeof(string []).Equals(factory.GetParameters()[0].ParameterType))
+            {
+                return FactoryResolutionResult<TWebhost, TWebhostBuilder>.NoCreateWebHostBuilder(programType);
+            }
+
+            return FactoryResolutionResult<TWebhost, TWebhostBuilder>.Succeded(args => (TWebhostBuilder)factory.Invoke(null, new object[] { args }), programType);
+        }
+
+        public static FactoryResolutionResult<TWebhost, TWebhostBuilder> ResolveWebHostFactory<TWebhost, TWebhostBuilder>(Assembly assembly)
+        {
+            // We want to give priority to BuildWebHost over CreateWebHostBuilder for backwards
+            // compatibility with existing projects that follow the old pattern.
+            var findResult = ResolveWebHostBuilderFactory<TWebhost, TWebhostBuilder>(assembly);
+            switch (findResult.ResultKind)
+            {
+                case FactoryResolutionResultKind.NoEntryPoint:
+                    return findResult;
+                case FactoryResolutionResultKind.Success:
+                case FactoryResolutionResultKind.NoCreateWebHostBuilder:
+                    var buildWebHostMethod = findResult.ProgramType.GetTypeInfo().GetDeclaredMethod(BuildWebHost);
+                    if (buildWebHostMethod == null ||
+                        !typeof(TWebhost).IsAssignableFrom(buildWebHostMethod.ReturnType) ||
+                        buildWebHostMethod.GetParameters().Length != 1 ||
+                        !typeof(string[]).Equals(buildWebHostMethod.GetParameters()[0].ParameterType))
+                    {
+                        if (findResult.ResultKind == FactoryResolutionResultKind.Success)
+                        {
+                            return findResult;
+                        }
+
+                        return FactoryResolutionResult<TWebhost, TWebhostBuilder>.NoBuildWebHost(findResult.ProgramType);
+                    }
+                    else
+                    {
+                        return FactoryResolutionResult<TWebhost, TWebhostBuilder>.Succeded(args => (TWebhost)buildWebHostMethod.Invoke(null, new object[] { args }), findResult.ProgramType);
+                    }
+                case FactoryResolutionResultKind.NoBuildWebHost:
+                default:
+                    throw new InvalidOperationException();
+            }
+        }
+    }
+}
diff --git a/src/templating/.gitignore b/src/Templating/.gitignore
similarity index 100%
rename from src/templating/.gitignore
rename to src/Templating/.gitignore
diff --git a/src/templating/Directory.Build.props b/src/Templating/Directory.Build.props
similarity index 91%
rename from src/templating/Directory.Build.props
rename to src/Templating/Directory.Build.props
index 92ebf42a309139657e03668aff48acfb6bfc4cca..9887edd5d59d8f7a7ba186cede1b8b89e5221aad 100644
--- a/src/templating/Directory.Build.props
+++ b/src/Templating/Directory.Build.props
@@ -10,7 +10,7 @@
   <PropertyGroup>
     <Product>Microsoft ASP.NET Core</Product>
     <RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
-    <RepositoryUrl>https://github.com/aspnet/templating</RepositoryUrl>
+    <RepositoryUrl>https://github.com/aspnet/Templating</RepositoryUrl>
     <RepositoryType>git</RepositoryType>
     <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
   </PropertyGroup>
diff --git a/src/templating/Directory.Build.targets b/src/Templating/Directory.Build.targets
similarity index 100%
rename from src/templating/Directory.Build.targets
rename to src/Templating/Directory.Build.targets
diff --git a/src/templating/NuGetPackageVerifier.json b/src/Templating/NuGetPackageVerifier.json
similarity index 100%
rename from src/templating/NuGetPackageVerifier.json
rename to src/Templating/NuGetPackageVerifier.json
diff --git a/src/templating/README.md b/src/Templating/README.md
similarity index 100%
rename from src/templating/README.md
rename to src/Templating/README.md
diff --git a/src/templating/Templating.sln b/src/Templating/Templating.sln
similarity index 100%
rename from src/templating/Templating.sln
rename to src/Templating/Templating.sln
diff --git a/src/templating/build/dependencies.props b/src/Templating/build/dependencies.props
similarity index 100%
rename from src/templating/build/dependencies.props
rename to src/Templating/build/dependencies.props
diff --git a/src/templating/build/repo.props b/src/Templating/build/repo.props
similarity index 100%
rename from src/templating/build/repo.props
rename to src/Templating/build/repo.props
diff --git a/src/templating/build/sources.props b/src/Templating/build/sources.props
similarity index 100%
rename from src/templating/build/sources.props
rename to src/Templating/build/sources.props
diff --git a/src/templating/src/Directory.Build.props b/src/Templating/src/Directory.Build.props
similarity index 100%
rename from src/templating/src/Directory.Build.props
rename to src/Templating/src/Directory.Build.props
diff --git a/src/templating/src/Directory.Build.targets b/src/Templating/src/Directory.Build.targets
similarity index 100%
rename from src/templating/src/Directory.Build.targets
rename to src/Templating/src/Directory.Build.targets
diff --git a/src/templating/src/GenerateContent.targets b/src/Templating/src/GenerateContent.targets
similarity index 100%
rename from src/templating/src/GenerateContent.targets
rename to src/Templating/src/GenerateContent.targets
diff --git a/src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/Microsoft.DotNet.Web.Client.ItemTemplates.csproj b/src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/Microsoft.DotNet.Web.Client.ItemTemplates.csproj
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/Microsoft.DotNet.Web.Client.ItemTemplates.csproj
rename to src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/Microsoft.DotNet.Web.Client.ItemTemplates.csproj
diff --git a/src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/.template.config/dotnetcli.host.json b/src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/.template.config/dotnetcli.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/.template.config/dotnetcli.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/.template.config/dotnetcli.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/.template.config/template.json b/src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/.template.config/template.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/.template.config/template.json
rename to src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/.template.config/template.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/styleSheet1.less b/src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/styleSheet1.less
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/styleSheet1.less
rename to src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/styleSheet1.less
diff --git a/src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/.template.config/dotnetcli.host.json b/src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/.template.config/dotnetcli.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/.template.config/dotnetcli.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/.template.config/dotnetcli.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/.template.config/template.json b/src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/.template.config/template.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/.template.config/template.json
rename to src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/.template.config/template.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/styleSheet1.scss b/src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/styleSheet1.scss
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/styleSheet1.scss
rename to src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/styleSheet1.scss
diff --git a/src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/.template.config/dotnetcli.host.json b/src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/.template.config/dotnetcli.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/.template.config/dotnetcli.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/.template.config/dotnetcli.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/.template.config/template.json b/src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/.template.config/template.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/.template.config/template.json
rename to src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/.template.config/template.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/file1.ts b/src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/file1.ts
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/file1.ts
rename to src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/file1.ts
diff --git a/src/templating/src/Microsoft.DotNet.Web.ItemTemplates/Microsoft.DotNet.Web.ItemTemplates.csproj b/src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/Microsoft.DotNet.Web.ItemTemplates.csproj
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/Microsoft.DotNet.Web.ItemTemplates.csproj
rename to src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/Microsoft.DotNet.Web.ItemTemplates.csproj
diff --git a/src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/.template.config/dotnetcli.host.json b/src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/.template.config/dotnetcli.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/.template.config/dotnetcli.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/.template.config/dotnetcli.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/.template.config/template.json b/src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/.template.config/template.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/.template.config/template.json
rename to src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/.template.config/template.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/Index.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/Index.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/Index.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/Index.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/Index.cshtml.cs b/src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/Index.cshtml.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/Index.cshtml.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/Index.cshtml.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/.template.config/dotnetcli.host.json b/src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/.template.config/dotnetcli.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/.template.config/dotnetcli.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/.template.config/dotnetcli.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/.template.config/template.json b/src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/.template.config/template.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/.template.config/template.json
rename to src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/.template.config/template.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/_ViewImports.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/_ViewImports.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/_ViewImports.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/_ViewImports.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/.template.config/dotnetcli.host.json b/src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/.template.config/dotnetcli.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/.template.config/dotnetcli.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/.template.config/dotnetcli.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/.template.config/template.json b/src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/.template.config/template.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/.template.config/template.json
rename to src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/.template.config/template.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/_ViewStart.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/_ViewStart.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/_ViewStart.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/_ViewStart.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/.gitignore b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/.gitignore
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/.gitignore
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/.gitignore
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/EmptyWeb-CSharp.csproj.in b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/EmptyWeb-CSharp.csproj.in
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/EmptyWeb-CSharp.csproj.in
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/EmptyWeb-CSharp.csproj.in
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/EmptyWeb-FSharp.fsproj.in b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/EmptyWeb-FSharp.fsproj.in
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/EmptyWeb-FSharp.fsproj.in
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/EmptyWeb-FSharp.fsproj.in
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/Microsoft.DotNet.Web.ProjectTemplates.csproj b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/Microsoft.DotNet.Web.ProjectTemplates.csproj
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/Microsoft.DotNet.Web.ProjectTemplates.csproj
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/Microsoft.DotNet.Web.ProjectTemplates.csproj
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/RazorClassLibrary-CSharp.csproj.in b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/RazorClassLibrary-CSharp.csproj.in
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/RazorClassLibrary-CSharp.csproj.in
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/RazorClassLibrary-CSharp.csproj.in
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/StarterWeb-CSharp.csproj.in b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/StarterWeb-CSharp.csproj.in
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/StarterWeb-CSharp.csproj.in
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/StarterWeb-CSharp.csproj.in
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/StarterWeb-FSharp.fsproj.in b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/StarterWeb-FSharp.fsproj.in
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/StarterWeb-FSharp.fsproj.in
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/StarterWeb-FSharp.fsproj.in
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/WebApi-CSharp.csproj.in b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/WebApi-CSharp.csproj.in
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/WebApi-CSharp.csproj.in
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/WebApi-CSharp.csproj.in
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/WebApi-FSharp.fsproj.in b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/WebApi-FSharp.fsproj.in
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/WebApi-FSharp.fsproj.in
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/WebApi-FSharp.fsproj.in
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/Directory.Build.props b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/Directory.Build.props
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/Directory.Build.props
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/Directory.Build.props
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/Directory.Build.targets b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/Directory.Build.targets
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/Directory.Build.targets
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/Directory.Build.targets
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/dotnetcli.host.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/dotnetcli.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/dotnetcli.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/dotnetcli.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/vs-2017.3.host.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/vs-2017.3.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/vs-2017.3.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/vs-2017.3.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/vs-2017.3/Empty.png b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/vs-2017.3/Empty.png
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/vs-2017.3/Empty.png
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/vs-2017.3/Empty.png
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Program.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Program.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Program.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Program.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Properties/launchSettings.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Properties/launchSettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Properties/launchSettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Properties/launchSettings.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Startup.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Startup.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Startup.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Startup.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/app.config b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/app.config
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/app.config
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/app.config
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/wwwroot/-.- b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/wwwroot/-.-
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/wwwroot/-.-
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/wwwroot/-.-
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/dotnetcli.host.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/dotnetcli.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/dotnetcli.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/dotnetcli.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/template.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/template.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/template.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/template.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/vs-2017.3.host.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/vs-2017.3.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/vs-2017.3.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/vs-2017.3.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/vs-2017.3/Empty.png b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/vs-2017.3/Empty.png
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/vs-2017.3/Empty.png
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/vs-2017.3/Empty.png
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Program.fs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Program.fs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Program.fs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Program.fs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Properties/launchSettings.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Properties/launchSettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Properties/launchSettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Properties/launchSettings.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Startup.fs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Startup.fs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Startup.fs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Startup.fs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/app.config b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/app.config
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/app.config
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/app.config
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/dotnetcli.host.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/dotnetcli.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/dotnetcli.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/dotnetcli.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/template.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/template.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/template.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/template.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/vs-2017.3.host.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/vs-2017.3.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/vs-2017.3.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/vs-2017.3.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/vs-2017.3/RazorClassLibrary.ico b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/vs-2017.3/RazorClassLibrary.ico
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/vs-2017.3/RazorClassLibrary.ico
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/vs-2017.3/RazorClassLibrary.ico
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/Areas/MyFeature/Pages/Page1.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/Areas/MyFeature/Pages/Page1.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/Areas/MyFeature/Pages/Page1.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/Areas/MyFeature/Pages/Page1.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/Areas/MyFeature/Pages/Page1.cshtml.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/Areas/MyFeature/Pages/Page1.cshtml.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/Areas/MyFeature/Pages/Page1.cshtml.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/Areas/MyFeature/Pages/Page1.cshtml.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/dotnetcli.host.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/dotnetcli.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/dotnetcli.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/dotnetcli.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/vs-2017.3.host.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/vs-2017.3.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/vs-2017.3.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/vs-2017.3.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/vs-2017.3/WebApplication.png b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/vs-2017.3/WebApplication.png
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/vs-2017.3/WebApplication.png
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/vs-2017.3/WebApplication.png
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Areas/Identity/Pages/_ViewStart.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Areas/Identity/Pages/_ViewStart.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Areas/Identity/Pages/_ViewStart.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Areas/Identity/Pages/_ViewStart.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/ApplicationDbContext.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/ApplicationDbContext.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/ApplicationDbContext.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/ApplicationDbContext.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/ApplicationDbContextModelSnapshot.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/ApplicationDbContextModelSnapshot.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/ApplicationDbContextModelSnapshot.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/ApplicationDbContextModelSnapshot.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/ApplicationDbContextModelSnapshot.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/ApplicationDbContextModelSnapshot.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/ApplicationDbContextModelSnapshot.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/ApplicationDbContextModelSnapshot.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/About.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/About.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/About.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/About.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/About.cshtml.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/About.cshtml.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/About.cshtml.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/About.cshtml.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Contact.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Contact.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Contact.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Contact.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Contact.cshtml.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Contact.cshtml.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Contact.cshtml.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Contact.cshtml.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Error.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Error.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Error.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Error.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Error.cshtml.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Error.cshtml.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Error.cshtml.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Error.cshtml.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Index.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Index.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Index.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Index.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Index.cshtml.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Index.cshtml.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Index.cshtml.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Index.cshtml.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Privacy.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Privacy.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Privacy.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Privacy.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Privacy.cshtml.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Privacy.cshtml.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Privacy.cshtml.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Privacy.cshtml.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_CookieConsentPartial.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_CookieConsentPartial.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_CookieConsentPartial.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_CookieConsentPartial.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_Layout.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_Layout.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_Layout.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_Layout.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_LoginPartial.Identity.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_LoginPartial.Identity.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_LoginPartial.Identity.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_LoginPartial.Identity.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_LoginPartial.OrgAuth.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_LoginPartial.OrgAuth.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_LoginPartial.OrgAuth.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_LoginPartial.OrgAuth.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_ValidationScriptsPartial.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_ValidationScriptsPartial.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_ValidationScriptsPartial.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_ValidationScriptsPartial.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/_ViewImports.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/_ViewImports.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/_ViewImports.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/_ViewImports.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/_ViewStart.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/_ViewStart.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/_ViewStart.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/_ViewStart.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Program.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Program.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Program.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Program.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Properties/launchSettings.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Properties/launchSettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Properties/launchSettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Properties/launchSettings.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Startup.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Startup.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Startup.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Startup.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/app.config b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/app.config
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/app.config
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/app.config
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/app.db b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/app.db
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/app.db
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/app.db
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/appsettings.Development.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/appsettings.Development.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/appsettings.Development.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/appsettings.Development.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/appsettings.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/appsettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/appsettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/appsettings.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/css/site.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/css/site.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/css/site.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/css/site.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/css/site.min.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/css/site.min.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/css/site.min.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/css/site.min.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/favicon.ico b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/favicon.ico
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/favicon.ico
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/favicon.ico
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner1.svg b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner1.svg
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner1.svg
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner1.svg
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner2.svg b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner2.svg
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner2.svg
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner2.svg
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner3.svg b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner3.svg
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner3.svg
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner3.svg
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/.bower.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/.bower.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/.bower.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/.bower.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/LICENSE b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/LICENSE
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/LICENSE
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/LICENSE
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/npm.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/npm.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/npm.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/npm.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/.bower.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/.bower.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/.bower.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/.bower.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/LICENSE.md b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/LICENSE.md
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/LICENSE.md
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/LICENSE.md
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/.bower.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/.bower.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/.bower.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/.bower.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/LICENSE.txt b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/LICENSE.txt
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/LICENSE.txt
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/LICENSE.txt
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.map b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.map
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.map
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.map
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/dotnetcli.host.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/dotnetcli.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/dotnetcli.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/dotnetcli.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/vs-2017.3.host.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/vs-2017.3.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/vs-2017.3.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/vs-2017.3.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/vs-2017.3/WebApplication.png b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/vs-2017.3/WebApplication.png
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/vs-2017.3/WebApplication.png
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/vs-2017.3/WebApplication.png
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Areas/Identity/Pages/_ViewStart.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Areas/Identity/Pages/_ViewStart.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Areas/Identity/Pages/_ViewStart.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Areas/Identity/Pages/_ViewStart.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Controllers/HomeController.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Controllers/HomeController.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Controllers/HomeController.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Controllers/HomeController.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/ApplicationDbContext.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/ApplicationDbContext.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/ApplicationDbContext.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/ApplicationDbContext.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/ApplicationDbContextModelSnapshot.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/ApplicationDbContextModelSnapshot.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/ApplicationDbContextModelSnapshot.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/ApplicationDbContextModelSnapshot.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/ApplicationDbContextModelSnapshot.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/ApplicationDbContextModelSnapshot.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/ApplicationDbContextModelSnapshot.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/ApplicationDbContextModelSnapshot.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Models/ErrorViewModel.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Models/ErrorViewModel.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Models/ErrorViewModel.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Models/ErrorViewModel.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Program.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Program.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Program.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Program.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Properties/launchSettings.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Properties/launchSettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Properties/launchSettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Properties/launchSettings.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Startup.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Startup.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Startup.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Startup.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/About.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/About.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/About.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/About.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Contact.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Contact.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Contact.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Contact.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Index.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Index.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Index.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Index.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Privacy.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Privacy.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Privacy.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Privacy.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/Error.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/Error.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/Error.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/Error.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_CookieConsentPartial.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_CookieConsentPartial.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_CookieConsentPartial.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_CookieConsentPartial.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_Layout.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_Layout.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_Layout.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_Layout.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_LoginPartial.Identity.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_LoginPartial.Identity.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_LoginPartial.Identity.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_LoginPartial.Identity.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_LoginPartial.OrgAuth.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_LoginPartial.OrgAuth.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_LoginPartial.OrgAuth.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_LoginPartial.OrgAuth.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_ValidationScriptsPartial.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_ValidationScriptsPartial.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_ValidationScriptsPartial.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_ValidationScriptsPartial.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/_ViewImports.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/_ViewImports.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/_ViewImports.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/_ViewImports.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/_ViewStart.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/_ViewStart.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/_ViewStart.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/_ViewStart.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/app.config b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/app.config
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/app.config
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/app.config
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/app.db b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/app.db
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/app.db
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/app.db
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/appsettings.Development.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/appsettings.Development.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/appsettings.Development.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/appsettings.Development.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/appsettings.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/appsettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/appsettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/appsettings.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/css/site.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/css/site.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/css/site.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/css/site.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/css/site.min.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/css/site.min.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/css/site.min.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/css/site.min.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/favicon.ico b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/favicon.ico
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/favicon.ico
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/favicon.ico
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner1.svg b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner1.svg
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner1.svg
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner1.svg
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner2.svg b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner2.svg
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner2.svg
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner2.svg
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner3.svg b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner3.svg
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner3.svg
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner3.svg
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/js/site.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/js/site.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/js/site.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/js/site.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/js/site.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/js/site.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/js/site.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/js/site.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/.bower.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/.bower.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/.bower.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/.bower.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/LICENSE b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/LICENSE
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/LICENSE
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/LICENSE
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/npm.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/npm.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/npm.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/npm.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/.bower.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/.bower.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/.bower.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/.bower.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/LICENSE.md b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/LICENSE.md
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/LICENSE.md
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/LICENSE.md
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/.bower.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/.bower.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/.bower.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/.bower.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/LICENSE.txt b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/LICENSE.txt
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/LICENSE.txt
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/LICENSE.txt
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.map b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.map
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.map
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.map
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.bowerrc b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.bowerrc
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.bowerrc
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.bowerrc
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/dotnetcli.host.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/dotnetcli.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/dotnetcli.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/dotnetcli.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/template.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/template.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/template.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/template.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Controllers/HomeController.fs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Controllers/HomeController.fs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Controllers/HomeController.fs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Controllers/HomeController.fs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Models/ErrorViewModel.fs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Models/ErrorViewModel.fs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Models/ErrorViewModel.fs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Models/ErrorViewModel.fs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Program.fs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Program.fs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Program.fs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Program.fs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Properties/launchSettings.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Properties/launchSettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Properties/launchSettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Properties/launchSettings.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Startup.fs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Startup.fs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Startup.fs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Startup.fs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/About.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/About.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/About.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/About.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/Contact.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/Contact.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/Contact.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/Contact.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/Index.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/Index.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/Index.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/Index.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/Error.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/Error.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/Error.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/Error.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_Layout.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_Layout.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_Layout.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_Layout.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_ValidationScriptsPartial.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_ValidationScriptsPartial.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_ValidationScriptsPartial.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_ValidationScriptsPartial.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/_ViewImports.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/_ViewImports.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/_ViewImports.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/_ViewImports.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/_ViewStart.cshtml b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/_ViewStart.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/_ViewStart.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/_ViewStart.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/app.config b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/app.config
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/app.config
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/app.config
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/appsettings.Development.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/appsettings.Development.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/appsettings.Development.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/appsettings.Development.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/appsettings.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/appsettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/appsettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/appsettings.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/css/site.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/css/site.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/css/site.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/css/site.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/css/site.min.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/css/site.min.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/css/site.min.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/css/site.min.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/favicon.ico b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/favicon.ico
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/favicon.ico
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/favicon.ico
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner1.svg b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner1.svg
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner1.svg
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner1.svg
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner2.svg b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner2.svg
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner2.svg
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner2.svg
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner3.svg b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner3.svg
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner3.svg
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner3.svg
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/.bower.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/.bower.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/.bower.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/.bower.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/LICENSE b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/LICENSE
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/LICENSE
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/LICENSE
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/npm.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/npm.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/npm.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/npm.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/.bower.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/.bower.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/.bower.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/.bower.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/LICENSE.md b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/LICENSE.md
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/LICENSE.md
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/LICENSE.md
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/.bower.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/.bower.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/.bower.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/.bower.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/LICENSE.txt b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/LICENSE.txt
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/LICENSE.txt
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/LICENSE.txt
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.min.js b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.min.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.min.js
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.min.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.min.map b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.min.map
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.min.map
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.min.map
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/vs-2017.3.host.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/vs-2017.3.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/vs-2017.3.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/vs-2017.3.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/vs-2017.3/WebAPI.png b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/vs-2017.3/WebAPI.png
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/vs-2017.3/WebAPI.png
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/vs-2017.3/WebAPI.png
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Controllers/ValuesController.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Controllers/ValuesController.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Controllers/ValuesController.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Controllers/ValuesController.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Program.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Program.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Program.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Program.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Properties/launchSettings.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Properties/launchSettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Properties/launchSettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Properties/launchSettings.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Startup.cs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Startup.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Startup.cs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Startup.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/app.config b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/app.config
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/app.config
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/app.config
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/appsettings.Development.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/appsettings.Development.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/appsettings.Development.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/appsettings.Development.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/appsettings.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/appsettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/appsettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/appsettings.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/wwwroot/-.- b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/wwwroot/-.-
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/wwwroot/-.-
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/wwwroot/-.-
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/dotnetcli.host.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/dotnetcli.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/dotnetcli.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/dotnetcli.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/template.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/template.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/template.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/template.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/vs-2017.3.host.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/vs-2017.3.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/vs-2017.3.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/vs-2017.3.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/vs-2017.3/WebAPI.png b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/vs-2017.3/WebAPI.png
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/vs-2017.3/WebAPI.png
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/vs-2017.3/WebAPI.png
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Controllers/ValuesController.fs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Controllers/ValuesController.fs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Controllers/ValuesController.fs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Controllers/ValuesController.fs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Program.fs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Program.fs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Program.fs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Program.fs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Properties/launchSettings.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Properties/launchSettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Properties/launchSettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Properties/launchSettings.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Startup.fs b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Startup.fs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Startup.fs
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Startup.fs
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/app.config b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/app.config
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/app.config
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/app.config
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/appsettings.Development.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/appsettings.Development.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/appsettings.Development.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/appsettings.Development.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/appsettings.json b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/appsettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/appsettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/appsettings.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/wwwroot/-.- b/src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/wwwroot/-.-
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/wwwroot/-.-
rename to src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/wwwroot/-.-
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/.gitignore b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/.gitignore
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/.gitignore
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/.gitignore
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/Angular-CSharp.csproj.in b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/Angular-CSharp.csproj.in
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/Angular-CSharp.csproj.in
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/Angular-CSharp.csproj.in
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/React-CSharp.csproj.in b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/React-CSharp.csproj.in
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/React-CSharp.csproj.in
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/React-CSharp.csproj.in
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/ReactRedux-CSharp.csproj.in b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/ReactRedux-CSharp.csproj.in
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/ReactRedux-CSharp.csproj.in
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/ReactRedux-CSharp.csproj.in
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.gitignore b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.gitignore
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.gitignore
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.gitignore
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/dotnetcli.host.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/dotnetcli.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/dotnetcli.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/dotnetcli.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/icon.png b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/icon.png
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/icon.png
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/icon.png
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/template.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/template.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/template.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/template.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/vs-2017.3.host.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/vs-2017.3.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/vs-2017.3.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/vs-2017.3.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.angular-cli.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.angular-cli.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.angular-cli.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.angular-cli.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.editorconfig b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.editorconfig
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.editorconfig
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.editorconfig
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.gitignore b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.gitignore
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.gitignore
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.gitignore
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/README.md b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/README.md
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/README.md
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/README.md
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/app.e2e-spec.ts b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/app.e2e-spec.ts
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/app.e2e-spec.ts
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/app.e2e-spec.ts
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/app.po.ts b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/app.po.ts
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/app.po.ts
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/app.po.ts
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/tsconfig.e2e.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/tsconfig.e2e.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/tsconfig.e2e.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/tsconfig.e2e.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/karma.conf.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/karma.conf.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/karma.conf.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/karma.conf.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/package-lock.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/package-lock.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/package-lock.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/package-lock.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/package.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/package.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/package.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/package.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/protractor.conf.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/protractor.conf.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/protractor.conf.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/protractor.conf.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.css b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.css
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.html b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.html
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.html
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.html
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.ts b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.ts
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.ts
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.ts
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.module.ts b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.module.ts
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.module.ts
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.module.ts
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.html b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.html
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.html
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.html
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.spec.ts b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.spec.ts
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.spec.ts
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.spec.ts
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.ts b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.ts
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.ts
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.ts
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/fetch-data/fetch-data.component.html b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/fetch-data/fetch-data.component.html
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/fetch-data/fetch-data.component.html
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/fetch-data/fetch-data.component.html
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/fetch-data/fetch-data.component.ts b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/fetch-data/fetch-data.component.ts
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/fetch-data/fetch-data.component.ts
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/fetch-data/fetch-data.component.ts
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/home/home.component.html b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/home/home.component.html
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/home/home.component.html
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/home/home.component.html
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/home/home.component.ts b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/home/home.component.ts
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/home/home.component.ts
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/home/home.component.ts
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.css b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.css
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.html b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.html
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.html
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.html
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.ts b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.ts
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.ts
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.ts
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/assets/.gitkeep b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/assets/.gitkeep
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/assets/.gitkeep
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/assets/.gitkeep
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/environments/environment.prod.ts b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/environments/environment.prod.ts
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/environments/environment.prod.ts
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/environments/environment.prod.ts
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/environments/environment.ts b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/environments/environment.ts
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/environments/environment.ts
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/environments/environment.ts
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/index.html b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/index.html
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/index.html
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/index.html
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/main.ts b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/main.ts
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/main.ts
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/main.ts
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/polyfills.ts b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/polyfills.ts
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/polyfills.ts
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/polyfills.ts
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/styles.css b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/styles.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/styles.css
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/styles.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/test.ts b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/test.ts
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/test.ts
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/test.ts
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/tsconfig.app.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/tsconfig.app.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/tsconfig.app.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/tsconfig.app.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/tsconfig.spec.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/tsconfig.spec.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/tsconfig.spec.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/tsconfig.spec.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/typings.d.ts b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/typings.d.ts
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/typings.d.ts
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/typings.d.ts
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/tsconfig.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/tsconfig.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/tsconfig.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/tsconfig.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/tslint.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/tslint.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/tslint.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/tslint.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/SampleDataController.cs b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/SampleDataController.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/SampleDataController.cs
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/SampleDataController.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/Error.cshtml b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/Error.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/Error.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/Error.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/Error.cshtml.cs b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/Error.cshtml.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/Error.cshtml.cs
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/Error.cshtml.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/_ViewImports.cshtml b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/_ViewImports.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/_ViewImports.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/_ViewImports.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Program.cs b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Program.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Program.cs
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Program.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Properties/launchSettings.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Properties/launchSettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Properties/launchSettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Properties/launchSettings.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Startup.cs b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Startup.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Startup.cs
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Startup.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/app.config b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/app.config
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/app.config
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/app.config
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/appsettings.Development.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/appsettings.Development.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/appsettings.Development.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/appsettings.Development.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/appsettings.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/appsettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/appsettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/appsettings.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/wwwroot/favicon.ico b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/wwwroot/favicon.ico
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/wwwroot/favicon.ico
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/wwwroot/favicon.ico
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Directory.Build.props b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Directory.Build.props
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Directory.Build.props
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Directory.Build.props
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Directory.Build.targets b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Directory.Build.targets
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Directory.Build.targets
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Directory.Build.targets
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.gitignore b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.gitignore
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.gitignore
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.gitignore
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/dotnetcli.host.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/dotnetcli.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/dotnetcli.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/dotnetcli.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/icon.png b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/icon.png
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/icon.png
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/icon.png
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/template.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/template.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/template.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/template.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/vs-2017.3.host.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/vs-2017.3.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/vs-2017.3.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/vs-2017.3.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/.gitignore b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/.gitignore
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/.gitignore
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/.gitignore
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/README.md b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/README.md
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/README.md
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/README.md
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/package-lock.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/package-lock.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/package-lock.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/package-lock.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/package.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/package.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/package.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/package.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/favicon.ico b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/favicon.ico
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/favicon.ico
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/favicon.ico
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/index.html b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/index.html
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/index.html
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/index.html
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/manifest.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/manifest.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/manifest.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/manifest.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/App.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/App.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/App.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/App.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/App.test.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/App.test.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/App.test.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/App.test.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Counter.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Counter.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Counter.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Counter.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/FetchData.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/FetchData.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/FetchData.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/FetchData.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Home.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Home.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Home.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Home.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Layout.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Layout.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Layout.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Layout.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/NavMenu.css b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/NavMenu.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/NavMenu.css
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/NavMenu.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/NavMenu.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/NavMenu.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/NavMenu.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/NavMenu.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/index.css b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/index.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/index.css
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/index.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/index.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/index.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/index.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/index.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/registerServiceWorker.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/registerServiceWorker.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/registerServiceWorker.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/registerServiceWorker.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Controllers/SampleDataController.cs b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Controllers/SampleDataController.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Controllers/SampleDataController.cs
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Controllers/SampleDataController.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/Error.cshtml b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/Error.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/Error.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/Error.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/Error.cshtml.cs b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/Error.cshtml.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/Error.cshtml.cs
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/Error.cshtml.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/_ViewImports.cshtml b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/_ViewImports.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/_ViewImports.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/_ViewImports.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Program.cs b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Program.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Program.cs
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Program.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Properties/launchSettings.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Properties/launchSettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Properties/launchSettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Properties/launchSettings.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Startup.cs b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Startup.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Startup.cs
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Startup.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/app.config b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/app.config
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/app.config
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/app.config
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/appsettings.Development.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/appsettings.Development.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/appsettings.Development.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/appsettings.Development.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/appsettings.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/appsettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/appsettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/appsettings.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.gitignore b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.gitignore
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.gitignore
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.gitignore
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/dotnetcli.host.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/dotnetcli.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/dotnetcli.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/dotnetcli.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/icon.png b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/icon.png
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/icon.png
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/icon.png
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/template.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/template.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/template.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/template.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/vs-2017.3.host.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/vs-2017.3.host.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/vs-2017.3.host.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/vs-2017.3.host.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.gitignore b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.gitignore
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.gitignore
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.gitignore
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/README.md b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/README.md
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/README.md
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/README.md
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/package-lock.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/package-lock.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/package-lock.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/package-lock.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/package.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/package.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/package.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/package.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/favicon.ico b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/favicon.ico
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/favicon.ico
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/favicon.ico
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/index.html b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/index.html
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/index.html
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/index.html
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/manifest.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/manifest.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/manifest.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/manifest.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/App.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/App.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/App.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/App.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/App.test.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/App.test.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/App.test.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/App.test.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Counter.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Counter.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Counter.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Counter.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/FetchData.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/FetchData.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/FetchData.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/FetchData.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Home.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Home.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Home.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Home.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Layout.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Layout.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Layout.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Layout.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/NavMenu.css b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/NavMenu.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/NavMenu.css
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/NavMenu.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/NavMenu.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/NavMenu.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/NavMenu.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/NavMenu.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/index.css b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/index.css
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/index.css
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/index.css
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/index.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/index.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/index.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/index.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/registerServiceWorker.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/registerServiceWorker.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/registerServiceWorker.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/registerServiceWorker.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/Counter.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/Counter.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/Counter.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/Counter.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/WeatherForecasts.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/WeatherForecasts.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/WeatherForecasts.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/WeatherForecasts.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/configureStore.js b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/configureStore.js
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/configureStore.js
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/configureStore.js
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Controllers/SampleDataController.cs b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Controllers/SampleDataController.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Controllers/SampleDataController.cs
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Controllers/SampleDataController.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/Error.cshtml b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/Error.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/Error.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/Error.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/Error.cshtml.cs b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/Error.cshtml.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/Error.cshtml.cs
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/Error.cshtml.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/_ViewImports.cshtml b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/_ViewImports.cshtml
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/_ViewImports.cshtml
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/_ViewImports.cshtml
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Program.cs b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Program.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Program.cs
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Program.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Properties/launchSettings.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Properties/launchSettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Properties/launchSettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Properties/launchSettings.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Startup.cs b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Startup.cs
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Startup.cs
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Startup.cs
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/app.config b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/app.config
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/app.config
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/app.config
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/appsettings.Development.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/appsettings.Development.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/appsettings.Development.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/appsettings.Development.json
diff --git a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/appsettings.json b/src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/appsettings.json
similarity index 100%
rename from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/appsettings.json
rename to src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/appsettings.json
diff --git a/src/templating/src/SetPackageProperties.targets b/src/Templating/src/SetPackageProperties.targets
similarity index 100%
rename from src/templating/src/SetPackageProperties.targets
rename to src/Templating/src/SetPackageProperties.targets
diff --git a/src/templating/src/THIRD-PARTY-NOTICES b/src/Templating/src/THIRD-PARTY-NOTICES
similarity index 100%
rename from src/templating/src/THIRD-PARTY-NOTICES
rename to src/Templating/src/THIRD-PARTY-NOTICES
diff --git a/src/templating/src/templates.nuspec b/src/Templating/src/templates.nuspec
similarity index 100%
rename from src/templating/src/templates.nuspec
rename to src/Templating/src/templates.nuspec
diff --git a/src/templating/test/Directory.Build.targets b/src/Templating/test/Directory.Build.targets
similarity index 100%
rename from src/templating/test/Directory.Build.targets
rename to src/Templating/test/Directory.Build.targets
diff --git a/src/templating/test/DotNetToolsInstaller/DotNetToolsInstaller.csproj b/src/Templating/test/DotNetToolsInstaller/DotNetToolsInstaller.csproj
similarity index 100%
rename from src/templating/test/DotNetToolsInstaller/DotNetToolsInstaller.csproj
rename to src/Templating/test/DotNetToolsInstaller/DotNetToolsInstaller.csproj
diff --git a/src/templating/test/GenerateTestProps.targets b/src/Templating/test/GenerateTestProps.targets
similarity index 100%
rename from src/templating/test/GenerateTestProps.targets
rename to src/Templating/test/GenerateTestProps.targets
diff --git a/src/templating/test/TemplateTests.props.in b/src/Templating/test/TemplateTests.props.in
similarity index 100%
rename from src/templating/test/TemplateTests.props.in
rename to src/Templating/test/TemplateTests.props.in
diff --git a/src/templating/test/Templates.Test/.gitattributes b/src/Templating/test/Templates.Test/.gitattributes
similarity index 100%
rename from src/templating/test/Templates.Test/.gitattributes
rename to src/Templating/test/Templates.Test/.gitattributes
diff --git a/src/templating/test/Templates.Test/BaselineTest.cs b/src/Templating/test/Templates.Test/BaselineTest.cs
similarity index 100%
rename from src/templating/test/Templates.Test/BaselineTest.cs
rename to src/Templating/test/Templates.Test/BaselineTest.cs
diff --git a/src/templating/test/Templates.Test/ByteOrderMarkTest.cs b/src/Templating/test/Templates.Test/ByteOrderMarkTest.cs
similarity index 100%
rename from src/templating/test/Templates.Test/ByteOrderMarkTest.cs
rename to src/Templating/test/Templates.Test/ByteOrderMarkTest.cs
diff --git a/src/templating/test/Templates.Test/CdnScriptTagTests.cs b/src/Templating/test/Templates.Test/CdnScriptTagTests.cs
similarity index 100%
rename from src/templating/test/Templates.Test/CdnScriptTagTests.cs
rename to src/Templating/test/Templates.Test/CdnScriptTagTests.cs
diff --git a/src/templating/test/Templates.Test/EmptyWebTemplateTest.cs b/src/Templating/test/Templates.Test/EmptyWebTemplateTest.cs
similarity index 100%
rename from src/templating/test/Templates.Test/EmptyWebTemplateTest.cs
rename to src/Templating/test/Templates.Test/EmptyWebTemplateTest.cs
diff --git a/src/templating/test/Templates.Test/Helpers/AddFirewallExclusion.cs b/src/Templating/test/Templates.Test/Helpers/AddFirewallExclusion.cs
similarity index 100%
rename from src/templating/test/Templates.Test/Helpers/AddFirewallExclusion.cs
rename to src/Templating/test/Templates.Test/Helpers/AddFirewallExclusion.cs
diff --git a/src/templating/test/Templates.Test/Helpers/AspNetProcess.cs b/src/Templating/test/Templates.Test/Helpers/AspNetProcess.cs
similarity index 100%
rename from src/templating/test/Templates.Test/Helpers/AspNetProcess.cs
rename to src/Templating/test/Templates.Test/Helpers/AspNetProcess.cs
diff --git a/src/templating/test/Templates.Test/Helpers/Npm.cs b/src/Templating/test/Templates.Test/Helpers/Npm.cs
similarity index 100%
rename from src/templating/test/Templates.Test/Helpers/Npm.cs
rename to src/Templating/test/Templates.Test/Helpers/Npm.cs
diff --git a/src/templating/test/Templates.Test/Helpers/ProcessEx.cs b/src/Templating/test/Templates.Test/Helpers/ProcessEx.cs
similarity index 100%
rename from src/templating/test/Templates.Test/Helpers/ProcessEx.cs
rename to src/Templating/test/Templates.Test/Helpers/ProcessEx.cs
diff --git a/src/templating/test/Templates.Test/Helpers/TemplatePackageInstaller.cs b/src/Templating/test/Templates.Test/Helpers/TemplatePackageInstaller.cs
similarity index 100%
rename from src/templating/test/Templates.Test/Helpers/TemplatePackageInstaller.cs
rename to src/Templating/test/Templates.Test/Helpers/TemplatePackageInstaller.cs
diff --git a/src/templating/test/Templates.Test/Helpers/TemplateTestBase.cs b/src/Templating/test/Templates.Test/Helpers/TemplateTestBase.cs
similarity index 100%
rename from src/templating/test/Templates.Test/Helpers/TemplateTestBase.cs
rename to src/Templating/test/Templates.Test/Helpers/TemplateTestBase.cs
diff --git a/src/templating/test/Templates.Test/Helpers/WebDriverExtensions.cs b/src/Templating/test/Templates.Test/Helpers/WebDriverExtensions.cs
similarity index 100%
rename from src/templating/test/Templates.Test/Helpers/WebDriverExtensions.cs
rename to src/Templating/test/Templates.Test/Helpers/WebDriverExtensions.cs
diff --git a/src/templating/test/Templates.Test/Helpers/WebDriverFactory.cs b/src/Templating/test/Templates.Test/Helpers/WebDriverFactory.cs
similarity index 100%
rename from src/templating/test/Templates.Test/Helpers/WebDriverFactory.cs
rename to src/Templating/test/Templates.Test/Helpers/WebDriverFactory.cs
diff --git a/src/templating/test/Templates.Test/MvcTemplateTest.cs b/src/Templating/test/Templates.Test/MvcTemplateTest.cs
similarity index 100%
rename from src/templating/test/Templates.Test/MvcTemplateTest.cs
rename to src/Templating/test/Templates.Test/MvcTemplateTest.cs
diff --git a/src/templating/test/Templates.Test/RazorPagesTemplateTest.cs b/src/Templating/test/Templates.Test/RazorPagesTemplateTest.cs
similarity index 100%
rename from src/templating/test/Templates.Test/RazorPagesTemplateTest.cs
rename to src/Templating/test/Templates.Test/RazorPagesTemplateTest.cs
diff --git a/src/templating/test/Templates.Test/SpaTemplateTest/AngularTemplateTest.cs b/src/Templating/test/Templates.Test/SpaTemplateTest/AngularTemplateTest.cs
similarity index 100%
rename from src/templating/test/Templates.Test/SpaTemplateTest/AngularTemplateTest.cs
rename to src/Templating/test/Templates.Test/SpaTemplateTest/AngularTemplateTest.cs
diff --git a/src/templating/test/Templates.Test/SpaTemplateTest/ReactReduxTemplateTest.cs b/src/Templating/test/Templates.Test/SpaTemplateTest/ReactReduxTemplateTest.cs
similarity index 100%
rename from src/templating/test/Templates.Test/SpaTemplateTest/ReactReduxTemplateTest.cs
rename to src/Templating/test/Templates.Test/SpaTemplateTest/ReactReduxTemplateTest.cs
diff --git a/src/templating/test/Templates.Test/SpaTemplateTest/ReactTemplateTest.cs b/src/Templating/test/Templates.Test/SpaTemplateTest/ReactTemplateTest.cs
similarity index 100%
rename from src/templating/test/Templates.Test/SpaTemplateTest/ReactTemplateTest.cs
rename to src/Templating/test/Templates.Test/SpaTemplateTest/ReactTemplateTest.cs
diff --git a/src/templating/test/Templates.Test/SpaTemplateTest/SpaTemplateTestBase.cs b/src/Templating/test/Templates.Test/SpaTemplateTest/SpaTemplateTestBase.cs
similarity index 100%
rename from src/templating/test/Templates.Test/SpaTemplateTest/SpaTemplateTestBase.cs
rename to src/Templating/test/Templates.Test/SpaTemplateTest/SpaTemplateTestBase.cs
diff --git a/src/templating/test/Templates.Test/Templates.Test.csproj b/src/Templating/test/Templates.Test/Templates.Test.csproj
similarity index 100%
rename from src/templating/test/Templates.Test/Templates.Test.csproj
rename to src/Templating/test/Templates.Test/Templates.Test.csproj
diff --git a/src/templating/test/Templates.Test/WebApiTemplateTest.cs b/src/Templating/test/Templates.Test/WebApiTemplateTest.cs
similarity index 100%
rename from src/templating/test/Templates.Test/WebApiTemplateTest.cs
rename to src/Templating/test/Templates.Test/WebApiTemplateTest.cs
diff --git a/src/templating/test/Templates.Test/template-baselines.json b/src/Templating/test/Templates.Test/template-baselines.json
similarity index 100%
rename from src/templating/test/Templates.Test/template-baselines.json
rename to src/Templating/test/Templates.Test/template-baselines.json
diff --git a/src/templating/version.props b/src/Templating/version.props
similarity index 100%
rename from src/templating/version.props
rename to src/Templating/version.props