Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/aspnetcore.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitmodules8
-rw-r--r--Directory.Build.props2
-rw-r--r--build/artifacts.props4
-rw-r--r--build/buildorder.props2
-rw-r--r--build/dependencies.props13
-rw-r--r--build/external-dependencies.props5
-rw-r--r--build/repo.props4
-rw-r--r--build/submodules.props2
-rw-r--r--eng/Baseline.props132
-rw-r--r--eng/Dependencies.props11
-rw-r--r--eng/ProjectReferences.props15
-rw-r--r--eng/dependencies.temp.props6
-rw-r--r--eng/tools/BaselineGenerator/baseline.xml14
m---------modules/Hosting9
m---------modules/HttpAbstractions26
-rw-r--r--src/Hosting/Abstractions/src/EnvironmentName.cs15
-rw-r--r--src/Hosting/Abstractions/src/HostingAbstractionsWebHostBuilderExtensions.cs197
-rw-r--r--src/Hosting/Abstractions/src/HostingEnvironmentExtensions.cs80
-rw-r--r--src/Hosting/Abstractions/src/HostingStartupAttribute.cs40
-rw-r--r--src/Hosting/Abstractions/src/IApplicationLifetime.cs37
-rw-r--r--src/Hosting/Abstractions/src/IHostingEnvironment.cs45
-rw-r--r--src/Hosting/Abstractions/src/IHostingStartup.cs22
-rw-r--r--src/Hosting/Abstractions/src/IStartup.cs16
-rw-r--r--src/Hosting/Abstractions/src/IStartupFilter.cs13
-rw-r--r--src/Hosting/Abstractions/src/IWebHost.cs43
-rw-r--r--src/Hosting/Abstractions/src/IWebHostBuilder.cs63
-rw-r--r--src/Hosting/Abstractions/src/Internal/IStartupConfigureContainerFilter.cs16
-rw-r--r--src/Hosting/Abstractions/src/Internal/IStartupConfigureServicesFilter.cs17
-rw-r--r--src/Hosting/Abstractions/src/Microsoft.AspNetCore.Hosting.Abstractions.csproj17
-rw-r--r--src/Hosting/Abstractions/src/WebHostBuilderContext.cs23
-rw-r--r--src/Hosting/Abstractions/src/WebHostDefaults.cs25
-rw-r--r--src/Hosting/Abstractions/src/baseline.netcore.json947
-rw-r--r--src/Hosting/Hosting/src/Builder/ApplicationBuilderFactory.cs25
-rw-r--r--src/Hosting/Hosting/src/Builder/IApplicationBuilderFactory.cs13
-rw-r--r--src/Hosting/Hosting/src/Internal/ApplicationLifetime.cs114
-rw-r--r--src/Hosting/Hosting/src/Internal/AutoRequestServicesStartupFilter.cs20
-rw-r--r--src/Hosting/Hosting/src/Internal/ConfigureBuilder.cs59
-rw-r--r--src/Hosting/Hosting/src/Internal/ConfigureContainerBuilder.cs52
-rw-r--r--src/Hosting/Hosting/src/Internal/ConfigureServicesBuilder.cs56
-rw-r--r--src/Hosting/Hosting/src/Internal/HostedServiceExecutor.cs76
-rw-r--r--src/Hosting/Hosting/src/Internal/HostingApplication.cs67
-rw-r--r--src/Hosting/Hosting/src/Internal/HostingApplicationDiagnostics.cs283
-rw-r--r--src/Hosting/Hosting/src/Internal/HostingEnvironment.cs22
-rw-r--r--src/Hosting/Hosting/src/Internal/HostingEnvironmentExtensions.cs65
-rw-r--r--src/Hosting/Hosting/src/Internal/HostingEventSource.cs55
-rw-r--r--src/Hosting/Hosting/src/Internal/HostingLoggerExtensions.cs166
-rw-r--r--src/Hosting/Hosting/src/Internal/HostingRequestFinishedLog.cs75
-rw-r--r--src/Hosting/Hosting/src/Internal/HostingRequestStartingLog.cs91
-rw-r--r--src/Hosting/Hosting/src/Internal/LoggerEventIds.cs21
-rw-r--r--src/Hosting/Hosting/src/Internal/RequestServicesContainerMiddleware.cs50
-rw-r--r--src/Hosting/Hosting/src/Internal/RequestServicesFeature.cs55
-rw-r--r--src/Hosting/Hosting/src/Internal/ServiceCollectionExtensions.cs20
-rw-r--r--src/Hosting/Hosting/src/Internal/StartupLoader.cs336
-rw-r--r--src/Hosting/Hosting/src/Internal/StartupMethods.cs28
-rw-r--r--src/Hosting/Hosting/src/Internal/WebHost.cs362
-rw-r--r--src/Hosting/Hosting/src/Internal/WebHostOptions.cs96
-rw-r--r--src/Hosting/Hosting/src/Internal/WebHostUtilities.cs17
-rw-r--r--src/Hosting/Hosting/src/Microsoft.AspNetCore.Hosting.csproj30
-rw-r--r--src/Hosting/Hosting/src/Properties/AssemblyInfo.cs6
-rw-r--r--src/Hosting/Hosting/src/Properties/Resources.Designer.cs94
-rw-r--r--src/Hosting/Hosting/src/Resources.resx132
-rw-r--r--src/Hosting/Hosting/src/Server/Features/ServerAddressesFeature.cs14
-rw-r--r--src/Hosting/Hosting/src/Startup/ConventionBasedStartup.cs56
-rw-r--r--src/Hosting/Hosting/src/Startup/DelegateStartup.cs21
-rw-r--r--src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.Designer.cs1055
-rw-r--r--src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.cshtml162
-rw-r--r--src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.css195
-rw-r--r--src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPage.js192
-rw-r--r--src/Hosting/Hosting/src/Startup/ExceptionPage/Views/ErrorPageModel.cs29
-rw-r--r--src/Hosting/Hosting/src/Startup/StartupBase.cs50
-rw-r--r--src/Hosting/Hosting/src/WebHostBuilder.cs364
-rw-r--r--src/Hosting/Hosting/src/WebHostBuilderExtensions.cs148
-rw-r--r--src/Hosting/Hosting/src/WebHostExtensions.cs176
-rw-r--r--src/Hosting/Hosting/src/baseline.netcore.json1995
-rw-r--r--src/Hosting/Hosting/src/compiler/resources/GenericError.html146
-rw-r--r--src/Hosting/Hosting/src/compiler/resources/GenericError_Exception.html8
-rw-r--r--src/Hosting/Hosting/src/compiler/resources/GenericError_Footer.html3
-rw-r--r--src/Hosting/Hosting/src/compiler/resources/GenericError_Message.html3
-rw-r--r--src/Hosting/Hosting/test/ConfigureBuilderTests.cs55
-rw-r--r--src/Hosting/Hosting/test/Fakes/CustomLoggerFactory.cs33
-rw-r--r--src/Hosting/Hosting/test/Fakes/FakeOptions.cs12
-rw-r--r--src/Hosting/Hosting/test/Fakes/FakeService.cs17
-rw-r--r--src/Hosting/Hosting/test/Fakes/IFactoryService.cs12
-rw-r--r--src/Hosting/Hosting/test/Fakes/IFakeEveryService.cs12
-rw-r--r--src/Hosting/Hosting/test/Fakes/IFakeScopedService.cs9
-rw-r--r--src/Hosting/Hosting/test/Fakes/IFakeService.cs7
-rw-r--r--src/Hosting/Hosting/test/Fakes/IFakeServiceInstance.cs9
-rw-r--r--src/Hosting/Hosting/test/Fakes/IFakeSingletonService.cs9
-rw-r--r--src/Hosting/Hosting/test/Fakes/IFakeStartupCallback.cs10
-rw-r--r--src/Hosting/Hosting/test/Fakes/INonexistentService.cs9
-rw-r--r--src/Hosting/Hosting/test/Fakes/Startup.cs99
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupBase.cs20
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupBoom.cs12
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupCaseInsensitive.cs27
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupConfigureServicesThrows.cs22
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupConfigureThrows.cs21
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupCtorThrows.cs20
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupNoServices.cs18
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupPrivateConfigure.cs25
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupStaticCtorThrows.cs20
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupThrowTypeLoadException.cs25
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupTwoConfigureServices.cs30
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupTwoConfigures.cs24
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupWithConfigureServices.cs35
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupWithConfigureServicesNotResolved.cs18
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupWithHostingEnvironment.cs18
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupWithILoggerFactory.cs31
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupWithNullConfigureServices.cs16
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupWithScopedServices.cs18
-rw-r--r--src/Hosting/Hosting/test/Fakes/StartupWithServices.cs23
-rw-r--r--src/Hosting/Hosting/test/HostingApplicationTests.cs371
-rw-r--r--src/Hosting/Hosting/test/HostingEnvironmentExtensionsTests.cs63
-rw-r--r--src/Hosting/Hosting/test/Internal/HostingEventSourceTests.cs217
-rw-r--r--src/Hosting/Hosting/test/Internal/HostingRequestStartLogTests.cs35
-rw-r--r--src/Hosting/Hosting/test/Internal/MyBadContainerFactory.cs24
-rw-r--r--src/Hosting/Hosting/test/Internal/MyContainer.cs40
-rw-r--r--src/Hosting/Hosting/test/Internal/MyContainerFactory.cs24
-rw-r--r--src/Hosting/Hosting/test/Microsoft.AspNetCore.Hosting.Tests.csproj22
-rw-r--r--src/Hosting/Hosting/test/RequestServicesContainerMiddlewareTests.cs122
-rw-r--r--src/Hosting/Hosting/test/StartupManagerTests.cs735
-rw-r--r--src/Hosting/Hosting/test/WebHostBuilderTests.cs1234
-rw-r--r--src/Hosting/Hosting/test/WebHostConfigurationsTests.cs58
-rw-r--r--src/Hosting/Hosting/test/WebHostTests.cs1328
-rw-r--r--src/Hosting/Hosting/test/testroot/TextFile.txt1
-rw-r--r--src/Hosting/Hosting/test/testroot/wwwroot/README1
-rw-r--r--src/Hosting/Server.Abstractions/src/Features/IServerAddressesFeature.cs14
-rw-r--r--src/Hosting/Server.Abstractions/src/IHttpApplication.cs36
-rw-r--r--src/Hosting/Server.Abstractions/src/IServer.cs35
-rw-r--r--src/Hosting/Server.Abstractions/src/Microsoft.AspNetCore.Hosting.Server.Abstractions.csproj16
-rw-r--r--src/Hosting/Server.Abstractions/src/baseline.netcore.json150
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Common/ApplicationType.cs11
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Common/DeploymentParameters.cs145
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Common/DeploymentResult.cs69
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Common/HostingModel.cs11
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Common/LoggingHandler.cs34
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Common/ProcessLoggingExtensions.cs34
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Common/RetryHelper.cs94
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Common/RuntimeArchitecture.cs11
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Common/RuntimeFlavor.cs11
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Common/ServerType.cs14
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Common/TestUriHelper.cs85
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Deployers/ApplicationDeployer.cs252
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Deployers/ApplicationDeployerFactory.cs51
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Deployers/IApplicationDeployer.cs20
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Deployers/IISExpressDeployer.cs317
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Deployers/NginxDeployer.cs165
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/RemotePSSessionHelper.ps167
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/RemoteWindowsDeployer.cs361
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/RemoteWindowsDeploymentParameters.cs40
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/StartServer.ps182
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Deployers/RemoteWindowsDeployer/StopServer.ps168
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Deployers/SelfHostDeployer.cs200
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/Microsoft.AspNetCore.Server.IntegrationTesting.csproj34
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/baseline.netcore.json1
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/xunit/SkipIfEnvironmentVariableNotEnabled.cs41
-rw-r--r--src/Hosting/Server.IntegrationTesting/src/xunit/SkipOn32BitOSAttribute.cs33
-rw-r--r--src/Hosting/TestHost/src/ClientHandler.cs132
-rw-r--r--src/Hosting/TestHost/src/HttpContextBuilder.cs130
-rw-r--r--src/Hosting/TestHost/src/Microsoft.AspNetCore.TestHost.csproj20
-rw-r--r--src/Hosting/TestHost/src/Properties/AssemblyInfo.cs10
-rw-r--r--src/Hosting/TestHost/src/RequestBuilder.cs105
-rw-r--r--src/Hosting/TestHost/src/RequestFeature.cs42
-rw-r--r--src/Hosting/TestHost/src/ResponseFeature.cs111
-rw-r--r--src/Hosting/TestHost/src/ResponseStream.cs256
-rw-r--r--src/Hosting/TestHost/src/TestServer.cs172
-rw-r--r--src/Hosting/TestHost/src/TestWebSocket.cs354
-rw-r--r--src/Hosting/TestHost/src/WebHostBuilderExtensions.cs138
-rw-r--r--src/Hosting/TestHost/src/WebHostBuilderFactory.cs26
-rw-r--r--src/Hosting/TestHost/src/WebSocketClient.cs134
-rw-r--r--src/Hosting/TestHost/src/baseline.netcore.json316
-rw-r--r--src/Hosting/TestHost/test/ClientHandlerTests.cs372
-rw-r--r--src/Hosting/TestHost/test/HttpContextBuilderTests.cs332
-rw-r--r--src/Hosting/TestHost/test/Microsoft.AspNetCore.TestHost.Tests.csproj12
-rw-r--r--src/Hosting/TestHost/test/RequestBuilderTests.cs38
-rw-r--r--src/Hosting/TestHost/test/ResponseFeatureTests.cs68
-rw-r--r--src/Hosting/TestHost/test/TestClientTests.cs429
-rw-r--r--src/Hosting/TestHost/test/TestServerTests.cs687
-rw-r--r--src/Hosting/WindowsServices/src/Microsoft.AspNetCore.Hosting.WindowsServices.csproj21
-rw-r--r--src/Hosting/WindowsServices/src/WebHostService.cs84
-rw-r--r--src/Hosting/WindowsServices/src/WebHostWindowsServiceExtensions.cs42
-rw-r--r--src/Hosting/WindowsServices/src/baseline.netcore.json122
-rw-r--r--src/Hosting/WindowsServices/src/baseline.netframework.json122
-rw-r--r--src/Hosting/samples/GenericWebHost/FakeServer.cs32
-rw-r--r--src/Hosting/samples/GenericWebHost/GenericWebHost.csproj18
-rw-r--r--src/Hosting/samples/GenericWebHost/Program.cs41
-rw-r--r--src/Hosting/samples/GenericWebHost/WebHostExtensions.cs43
-rw-r--r--src/Hosting/samples/GenericWebHost/WebHostService.cs62
-rw-r--r--src/Hosting/samples/GenericWebHost/WebHostServiceOptions.cs11
-rw-r--r--src/Hosting/samples/SampleStartups/FakeServer.cs32
-rw-r--r--src/Hosting/samples/SampleStartups/SampleStartups.csproj15
-rw-r--r--src/Hosting/samples/SampleStartups/StartupBlockingOnStart.cs47
-rw-r--r--src/Hosting/samples/SampleStartups/StartupConfigureAddresses.cs36
-rw-r--r--src/Hosting/samples/SampleStartups/StartupExternallyControlled.cs50
-rw-r--r--src/Hosting/samples/SampleStartups/StartupFullControl.cs76
-rw-r--r--src/Hosting/samples/SampleStartups/StartupHelloWorld.cs32
-rw-r--r--src/Hosting/samples/SampleStartups/StartupInjection.cs69
-rw-r--r--src/Hosting/test/FunctionalTests/Microsoft.AspNetCore.Hosting.FunctionalTests.csproj17
-rw-r--r--src/Hosting/test/FunctionalTests/Properties/AssemblyInfo.cs6
-rw-r--r--src/Hosting/test/FunctionalTests/ShutdownTests.cs144
-rw-r--r--src/Hosting/test/FunctionalTests/WebHostBuilderTests.cs73
-rw-r--r--src/Hosting/test/WebHostBuilderFactory.Tests/Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Tests.csproj22
-rw-r--r--src/Hosting/test/WebHostBuilderFactory.Tests/WebHostFactoryResolverTests.cs84
-rw-r--r--src/Hosting/test/testassets/BuildWebHostInvalidSignature/BuildWebHostInvalidSignature.csproj14
-rw-r--r--src/Hosting/test/testassets/BuildWebHostInvalidSignature/Program.cs16
-rw-r--r--src/Hosting/test/testassets/BuildWebHostInvalidSignature/Startup.cs19
-rw-r--r--src/Hosting/test/testassets/BuildWebHostPatternTestSite/BuildWebHostPatternTestSite.csproj14
-rw-r--r--src/Hosting/test/testassets/BuildWebHostPatternTestSite/Program.cs16
-rw-r--r--src/Hosting/test/testassets/BuildWebHostPatternTestSite/Startup.cs19
-rw-r--r--src/Hosting/test/testassets/CreateWebHostBuilderInvalidSignature/CreateWebHostBuilderInvalidSignature.csproj14
-rw-r--r--src/Hosting/test/testassets/CreateWebHostBuilderInvalidSignature/Program.cs16
-rw-r--r--src/Hosting/test/testassets/CreateWebHostBuilderInvalidSignature/Startup.cs19
-rw-r--r--src/Hosting/test/testassets/IStartupInjectionAssemblyName/IStartupInjectionAssemblyName.csproj14
-rw-r--r--src/Hosting/test/testassets/IStartupInjectionAssemblyName/Program.cs28
-rw-r--r--src/Hosting/test/testassets/IStartupInjectionAssemblyName/Startup.cs21
-rw-r--r--src/Hosting/test/testassets/Microsoft.AspNetCore.Hosting.TestSites/Microsoft.AspNetCore.Hosting.TestSites.csproj16
-rw-r--r--src/Hosting/test/testassets/Microsoft.AspNetCore.Hosting.TestSites/Program.cs77
-rw-r--r--src/Hosting/test/testassets/Microsoft.AspNetCore.Hosting.TestSites/StartupShutdown.cs38
-rw-r--r--src/Hosting/test/testassets/TestStartupAssembly1/TestHostingStartup1.cs18
-rw-r--r--src/Hosting/test/testassets/TestStartupAssembly1/TestStartupAssembly1.csproj11
-rw-r--r--src/Http/Authentication.Abstractions/src/AuthenticateResult.cs107
-rw-r--r--src/Http/Authentication.Abstractions/src/AuthenticationHttpContextExtensions.cs197
-rw-r--r--src/Http/Authentication.Abstractions/src/AuthenticationOptions.cs93
-rw-r--r--src/Http/Authentication.Abstractions/src/AuthenticationProperties.cs212
-rw-r--r--src/Http/Authentication.Abstractions/src/AuthenticationScheme.cs56
-rw-r--r--src/Http/Authentication.Abstractions/src/AuthenticationSchemeBuilder.cs43
-rw-r--r--src/Http/Authentication.Abstractions/src/AuthenticationTicket.cs56
-rw-r--r--src/Http/Authentication.Abstractions/src/AuthenticationToken.cs22
-rw-r--r--src/Http/Authentication.Abstractions/src/IAuthenticationFeature.cs23
-rw-r--r--src/Http/Authentication.Abstractions/src/IAuthenticationHandler.cs42
-rw-r--r--src/Http/Authentication.Abstractions/src/IAuthenticationHandlerProvider.cs22
-rw-r--r--src/Http/Authentication.Abstractions/src/IAuthenticationRequestHandler.cs20
-rw-r--r--src/Http/Authentication.Abstractions/src/IAuthenticationSchemeProvider.cs86
-rw-r--r--src/Http/Authentication.Abstractions/src/IAuthenticationService.cs60
-rw-r--r--src/Http/Authentication.Abstractions/src/IAuthenticationSignInHandler.cs22
-rw-r--r--src/Http/Authentication.Abstractions/src/IAuthenticationSignOutHandler.cs21
-rw-r--r--src/Http/Authentication.Abstractions/src/IClaimsTransformation.cs23
-rw-r--r--src/Http/Authentication.Abstractions/src/Microsoft.AspNetCore.Authentication.Abstractions.csproj17
-rw-r--r--src/Http/Authentication.Abstractions/src/TokenExtensions.cs161
-rw-r--r--src/Http/Authentication.Abstractions/src/baseline.netcore.json1734
-rw-r--r--src/Http/Authentication.Core/src/AuthenticationCoreServiceCollectionExtensions.cs56
-rw-r--r--src/Http/Authentication.Core/src/AuthenticationFeature.cs23
-rw-r--r--src/Http/Authentication.Core/src/AuthenticationHandlerProvider.cs63
-rw-r--r--src/Http/Authentication.Core/src/AuthenticationSchemeProvider.cs176
-rw-r--r--src/Http/Authentication.Core/src/AuthenticationService.cs303
-rw-r--r--src/Http/Authentication.Core/src/Microsoft.AspNetCore.Authentication.Core.csproj18
-rw-r--r--src/Http/Authentication.Core/src/NoopClaimsTransformation.cs24
-rw-r--r--src/Http/Authentication.Core/src/baseline.netcore.json515
-rw-r--r--src/Http/Authentication.Core/test/AuthenticationPropertiesTests.cs207
-rw-r--r--src/Http/Authentication.Core/test/AuthenticationSchemeProviderTests.cs207
-rw-r--r--src/Http/Authentication.Core/test/AuthenticationServiceTests.cs355
-rw-r--r--src/Http/Authentication.Core/test/Microsoft.AspNetCore.Authentication.Core.Test.csproj12
-rw-r--r--src/Http/Authentication.Core/test/TokenExtensionTests.cs200
-rw-r--r--src/Http/Headers/src/BaseHeaderParser.cs72
-rw-r--r--src/Http/Headers/src/CacheControlHeaderValue.cs664
-rw-r--r--src/Http/Headers/src/ContentDispositionHeaderValue.cs725
-rw-r--r--src/Http/Headers/src/ContentDispositionHeaderValueIdentityExtensions.cs46
-rw-r--r--src/Http/Headers/src/ContentRangeHeaderValue.cs407
-rw-r--r--src/Http/Headers/src/CookieHeaderParser.cs98
-rw-r--r--src/Http/Headers/src/CookieHeaderValue.cs277
-rw-r--r--src/Http/Headers/src/DateTimeFormatter.cs100
-rw-r--r--src/Http/Headers/src/EntityTagHeaderValue.cs250
-rw-r--r--src/Http/Headers/src/GenericHeaderParser.cs31
-rw-r--r--src/Http/Headers/src/HeaderNames.cs77
-rw-r--r--src/Http/Headers/src/HeaderQuality.cs18
-rw-r--r--src/Http/Headers/src/HeaderUtilities.cs732
-rw-r--r--src/Http/Headers/src/HttpHeaderParser.cs172
-rw-r--r--src/Http/Headers/src/HttpParseResult.cs12
-rw-r--r--src/Http/Headers/src/HttpRuleParser.cs349
-rw-r--r--src/Http/Headers/src/MediaTypeHeaderValue.cs721
-rw-r--r--src/Http/Headers/src/MediaTypeHeaderValueComparer.cs132
-rw-r--r--src/Http/Headers/src/Microsoft.Net.Http.Headers.csproj17
-rw-r--r--src/Http/Headers/src/NameValueHeaderValue.cs425
-rw-r--r--src/Http/Headers/src/ObjectCollection.cs91
-rw-r--r--src/Http/Headers/src/Properties/AssemblyInfo.cs6
-rw-r--r--src/Http/Headers/src/RangeConditionHeaderValue.cs167
-rw-r--r--src/Http/Headers/src/RangeHeaderValue.cs163
-rw-r--r--src/Http/Headers/src/RangeItemHeaderValue.cs229
-rw-r--r--src/Http/Headers/src/SameSiteMode.cs13
-rw-r--r--src/Http/Headers/src/SetCookieHeaderValue.cs523
-rw-r--r--src/Http/Headers/src/StringWithQualityHeaderValue.cs222
-rw-r--r--src/Http/Headers/src/StringWithQualityHeaderValueComparer.cs83
-rw-r--r--src/Http/Headers/src/baseline.netcore.json4110
-rw-r--r--src/Http/Headers/test/CacheControlHeaderValueTest.cs599
-rw-r--r--src/Http/Headers/test/ContentDispositionHeaderValueTest.cs622
-rw-r--r--src/Http/Headers/test/ContentRangeHeaderValueTest.cs272
-rw-r--r--src/Http/Headers/test/CookieHeaderValueTest.cs326
-rw-r--r--src/Http/Headers/test/DateParserTest.cs56
-rw-r--r--src/Http/Headers/test/EntityTagHeaderValueTest.cs533
-rw-r--r--src/Http/Headers/test/HeaderUtilitiesTest.cs285
-rw-r--r--src/Http/Headers/test/MediaTypeHeaderValueComparerTests.cs75
-rw-r--r--src/Http/Headers/test/MediaTypeHeaderValueTest.cs847
-rw-r--r--src/Http/Headers/test/Microsoft.Net.Http.Headers.Tests.csproj11
-rw-r--r--src/Http/Headers/test/NameValueHeaderValueTest.cs699
-rw-r--r--src/Http/Headers/test/RangeConditionHeaderValueTest.cs174
-rw-r--r--src/Http/Headers/test/RangeHeaderValueTest.cs183
-rw-r--r--src/Http/Headers/test/RangeItemHeaderValueTest.cs162
-rw-r--r--src/Http/Headers/test/SetCookieHeaderValueTest.cs429
-rw-r--r--src/Http/Headers/test/StringWithQualityHeaderValueComparerTest.cs64
-rw-r--r--src/Http/Headers/test/StringWithQualityHeaderValueTest.cs498
-rw-r--r--src/Http/Http.Abstractions/src/Authentication/AuthenticateInfo.cs29
-rw-r--r--src/Http/Http.Abstractions/src/Authentication/AuthenticationDescription.cs68
-rw-r--r--src/Http/Http.Abstractions/src/Authentication/AuthenticationManager.cs132
-rw-r--r--src/Http/Http.Abstractions/src/Authentication/AuthenticationProperties.cs197
-rw-r--r--src/Http/Http.Abstractions/src/ConnectionInfo.cs30
-rw-r--r--src/Http/Http.Abstractions/src/CookieBuilder.cs114
-rw-r--r--src/Http/Http.Abstractions/src/CookieSecurePolicy.cs34
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/HeaderDictionaryExtensions.cs56
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/HttpResponseWritingExtensions.cs67
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/MapExtensions.cs53
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/MapMiddleware.cs78
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/MapOptions.cs23
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/MapWhenExtensions.cs56
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/MapWhenMiddleware.cs61
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/MapWhenOptions.cs41
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/RunExtensions.cs34
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/UseExtensions.cs33
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs224
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/UsePathBaseExtensions.cs38
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/UsePathBaseMiddleware.cs77
-rw-r--r--src/Http/Http.Abstractions/src/Extensions/UseWhenExtensions.cs67
-rw-r--r--src/Http/Http.Abstractions/src/FragmentString.cs141
-rw-r--r--src/Http/Http.Abstractions/src/HostString.cs379
-rw-r--r--src/Http/Http.Abstractions/src/HttpContext.cs87
-rw-r--r--src/Http/Http.Abstractions/src/HttpMethods.cs65
-rw-r--r--src/Http/Http.Abstractions/src/HttpRequest.cs121
-rw-r--r--src/Http/Http.Abstractions/src/HttpResponse.cs109
-rw-r--r--src/Http/Http.Abstractions/src/IApplicationBuilder.cs51
-rw-r--r--src/Http/Http.Abstractions/src/IHttpContextAccessor.cs10
-rw-r--r--src/Http/Http.Abstractions/src/IHttpContextFactory.cs13
-rw-r--r--src/Http/Http.Abstractions/src/IMiddleware.cs21
-rw-r--r--src/Http/Http.Abstractions/src/IMiddlewareFactory.cs30
-rw-r--r--src/Http/Http.Abstractions/src/Internal/HeaderSegment.cs66
-rw-r--r--src/Http/Http.Abstractions/src/Internal/HeaderSegmentCollection.cs297
-rw-r--r--src/Http/Http.Abstractions/src/Internal/HostStringHelper.cs36
-rw-r--r--src/Http/Http.Abstractions/src/Internal/ParsingHelpers.cs165
-rw-r--r--src/Http/Http.Abstractions/src/Internal/PathStringHelper.cs47
-rw-r--r--src/Http/Http.Abstractions/src/Microsoft.AspNetCore.Http.Abstractions.csproj23
-rw-r--r--src/Http/Http.Abstractions/src/PathString.cs477
-rw-r--r--src/Http/Http.Abstractions/src/Properties/AssemblyInfo.cs6
-rw-r--r--src/Http/Http.Abstractions/src/Properties/Resources.Designer.cs212
-rw-r--r--src/Http/Http.Abstractions/src/QueryString.cs261
-rw-r--r--src/Http/Http.Abstractions/src/RequestDelegate.cs14
-rw-r--r--src/Http/Http.Abstractions/src/Resources.resx159
-rw-r--r--src/Http/Http.Abstractions/src/StatusCodes.cs79
-rw-r--r--src/Http/Http.Abstractions/src/WebSocketManager.cs41
-rw-r--r--src/Http/Http.Abstractions/src/baseline.netcore.json5020
-rw-r--r--src/Http/Http.Abstractions/test/CookieBuilderTests.cs57
-rw-r--r--src/Http/Http.Abstractions/test/FragmentStringTests.cs41
-rw-r--r--src/Http/Http.Abstractions/test/HostStringTest.cs175
-rw-r--r--src/Http/Http.Abstractions/test/HttpResponseWritingExtensionsTests.cs38
-rw-r--r--src/Http/Http.Abstractions/test/MapPathMiddlewareTests.cs199
-rw-r--r--src/Http/Http.Abstractions/test/MapPredicateMiddlewareTests.cs123
-rw-r--r--src/Http/Http.Abstractions/test/Microsoft.AspNetCore.Http.Abstractions.Tests.csproj11
-rw-r--r--src/Http/Http.Abstractions/test/PathStringTests.cs240
-rw-r--r--src/Http/Http.Abstractions/test/QueryStringTests.cs166
-rw-r--r--src/Http/Http.Abstractions/test/UseMiddlewareTest.cs376
-rw-r--r--src/Http/Http.Abstractions/test/UsePathBaseExtensionsTests.cs168
-rw-r--r--src/Http/Http.Abstractions/test/UseWhenExtensionsTests.cs170
-rw-r--r--src/Http/Http.Extensions/src/HeaderDictionaryTypeExtensions.cs287
-rw-r--r--src/Http/Http.Extensions/src/HttpRequestMultipartExtensions.cs26
-rw-r--r--src/Http/Http.Extensions/src/Microsoft.AspNetCore.Http.Extensions.csproj18
-rw-r--r--src/Http/Http.Extensions/src/QueryBuilder.cs81
-rw-r--r--src/Http/Http.Extensions/src/RequestHeaders.cs332
-rw-r--r--src/Http/Http.Extensions/src/ResponseExtensions.cs26
-rw-r--r--src/Http/Http.Extensions/src/ResponseHeaders.cs211
-rw-r--r--src/Http/Http.Extensions/src/SendFileResponseExtensions.cs181
-rw-r--r--src/Http/Http.Extensions/src/SessionExtensions.cs54
-rw-r--r--src/Http/Http.Extensions/src/StreamCopyOperation.cs87
-rw-r--r--src/Http/Http.Extensions/src/UriHelper.cs217
-rw-r--r--src/Http/Http.Extensions/src/baseline.netcore.json1699
-rw-r--r--src/Http/Http.Extensions/test/HeaderDictionaryTypeExtensionsTest.cs205
-rw-r--r--src/Http/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj13
-rw-r--r--src/Http/Http.Extensions/test/QueryBuilderTests.cs98
-rw-r--r--src/Http/Http.Extensions/test/ResponseExtensionTests.cs61
-rw-r--r--src/Http/Http.Extensions/test/SendFileResponseExtensionsTests.cs53
-rw-r--r--src/Http/Http.Extensions/test/UriHelperTests.cs156
-rw-r--r--src/Http/Http.Features/src/Authentication/AuthenticateContext.cs69
-rw-r--r--src/Http/Http.Features/src/Authentication/ChallengeBehavior.cs12
-rw-r--r--src/Http/Http.Features/src/Authentication/ChallengeContext.cs41
-rw-r--r--src/Http/Http.Features/src/Authentication/DescribeSchemesContext.cs27
-rw-r--r--src/Http/Http.Features/src/Authentication/IAuthenticationHandler.cs20
-rw-r--r--src/Http/Http.Features/src/Authentication/IHttpAuthenticationFeature.cs16
-rw-r--r--src/Http/Http.Features/src/Authentication/SignInContext.cs42
-rw-r--r--src/Http/Http.Features/src/Authentication/SignOutContext.cs33
-rw-r--r--src/Http/Http.Features/src/CookieOptions.cs69
-rw-r--r--src/Http/Http.Features/src/FeatureCollection.cs119
-rw-r--r--src/Http/Http.Features/src/FeatureReference.cs38
-rw-r--r--src/Http/Http.Features/src/FeatureReferences.cs98
-rw-r--r--src/Http/Http.Features/src/IFeatureCollection.cs45
-rw-r--r--src/Http/Http.Features/src/IFormCollection.cs94
-rw-r--r--src/Http/Http.Features/src/IFormFeature.cs34
-rw-r--r--src/Http/Http.Features/src/IFormFile.cs63
-rw-r--r--src/Http/Http.Features/src/IFormFileCollection.cs19
-rw-r--r--src/Http/Http.Features/src/IHeaderDictionary.cs26
-rw-r--r--src/Http/Http.Features/src/IHttpBodyControlFeature.cs16
-rw-r--r--src/Http/Http.Features/src/IHttpBufferingFeature.cs11
-rw-r--r--src/Http/Http.Features/src/IHttpConnectionFeature.cs38
-rw-r--r--src/Http/Http.Features/src/IHttpMaxRequestBodySizeFeature.cs29
-rw-r--r--src/Http/Http.Features/src/IHttpRequestFeature.cs77
-rw-r--r--src/Http/Http.Features/src/IHttpRequestIdentifierFeature.cs18
-rw-r--r--src/Http/Http.Features/src/IHttpRequestLifetimeFeature.cs23
-rw-r--r--src/Http/Http.Features/src/IHttpResponseFeature.cs59
-rw-r--r--src/Http/Http.Features/src/IHttpSendFileFeature.cs26
-rw-r--r--src/Http/Http.Features/src/IHttpUpgradeFeature.cs24
-rw-r--r--src/Http/Http.Features/src/IHttpWebSocketFeature.cs24
-rw-r--r--src/Http/Http.Features/src/IItemsFeature.cs12
-rw-r--r--src/Http/Http.Features/src/IQueryCollection.cs88
-rw-r--r--src/Http/Http.Features/src/IQueryFeature.cs10
-rw-r--r--src/Http/Http.Features/src/IRequestCookieCollection.cs87
-rw-r--r--src/Http/Http.Features/src/IRequestCookiesFeature.cs10
-rw-r--r--src/Http/Http.Features/src/IResponseCookies.cs42
-rw-r--r--src/Http/Http.Features/src/IResponseCookiesFeature.cs16
-rw-r--r--src/Http/Http.Features/src/IServiceProvidersFeature.cs12
-rw-r--r--src/Http/Http.Features/src/ISession.cs68
-rw-r--r--src/Http/Http.Features/src/ISessionFeature.cs10
-rw-r--r--src/Http/Http.Features/src/ITlsConnectionFeature.cs23
-rw-r--r--src/Http/Http.Features/src/ITlsTokenBindingFeature.cs35
-rw-r--r--src/Http/Http.Features/src/ITrackingConsentFeature.cs44
-rw-r--r--src/Http/Http.Features/src/Microsoft.AspNetCore.Http.Features.csproj15
-rw-r--r--src/Http/Http.Features/src/SameSiteMode.cs14
-rw-r--r--src/Http/Http.Features/src/WebSocketAcceptContext.cs10
-rw-r--r--src/Http/Http.Features/src/baseline.netcore.json2727
-rw-r--r--src/Http/Http.Features/test/Authentication/AuthenticateContextTest.cs162
-rw-r--r--src/Http/Http.Features/test/FeatureCollectionTests.cs48
-rw-r--r--src/Http/Http.Features/test/IThing.cs10
-rw-r--r--src/Http/Http.Features/test/Microsoft.AspNetCore.Http.Features.Tests.csproj11
-rw-r--r--src/Http/Http.Features/test/Thing.cs13
-rw-r--r--src/Http/Http/src/Authentication/DefaultAuthenticationManager.cs184
-rw-r--r--src/Http/Http/src/DefaultHttpContext.cs223
-rw-r--r--src/Http/Http/src/Extensions/HttpRequestRewindExtensions.cs91
-rw-r--r--src/Http/Http/src/Features/Authentication/HttpAuthenticationFeature.cs22
-rw-r--r--src/Http/Http/src/Features/DefaultSessionFeature.cs14
-rw-r--r--src/Http/Http/src/Features/FormFeature.cs323
-rw-r--r--src/Http/Http/src/Features/FormOptions.cs78
-rw-r--r--src/Http/Http/src/Features/HttpConnectionFeature.cs20
-rw-r--r--src/Http/Http/src/Features/HttpRequestFeature.cs33
-rw-r--r--src/Http/Http/src/Features/HttpRequestIdentifierFeature.cs64
-rw-r--r--src/Http/Http/src/Features/HttpRequestLifetimeFeature.cs16
-rw-r--r--src/Http/Http/src/Features/HttpResponseFeature.cs40
-rw-r--r--src/Http/Http/src/Features/ItemsFeature.cs18
-rw-r--r--src/Http/Http/src/Features/QueryFeature.cs93
-rw-r--r--src/Http/Http/src/Features/RequestCookiesFeature.cs96
-rw-r--r--src/Http/Http/src/Features/ResponseCookiesFeature.cs69
-rw-r--r--src/Http/Http/src/Features/ServiceProvidersFeature.cs12
-rw-r--r--src/Http/Http/src/Features/TlsConnectionFeature.cs19
-rw-r--r--src/Http/Http/src/FormCollection.cs228
-rw-r--r--src/Http/Http/src/HeaderDictionary.cs416
-rw-r--r--src/Http/Http/src/HttpContextAccessor.cs24
-rw-r--r--src/Http/Http/src/HttpContextFactory.cs58
-rw-r--r--src/Http/Http/src/HttpServiceCollectionExtensions.cs31
-rw-r--r--src/Http/Http/src/Internal/ApplicationBuilder.cs96
-rw-r--r--src/Http/Http/src/Internal/BindingAddress.cs155
-rw-r--r--src/Http/Http/src/Internal/BufferingHelper.cs80
-rw-r--r--src/Http/Http/src/Internal/Constants.cs18
-rw-r--r--src/Http/Http/src/Internal/DefaultConnectionInfo.cs90
-rw-r--r--src/Http/Http/src/Internal/DefaultHttpRequest.cs162
-rw-r--r--src/Http/Http/src/Internal/DefaultHttpResponse.cs139
-rw-r--r--src/Http/Http/src/Internal/DefaultWebSocketManager.cs73
-rw-r--r--src/Http/Http/src/Internal/FormFile.cs109
-rw-r--r--src/Http/Http/src/Internal/FormFileCollection.cs41
-rw-r--r--src/Http/Http/src/Internal/ItemsDictionary.cs118
-rw-r--r--src/Http/Http/src/Internal/QueryCollection.cs222
-rw-r--r--src/Http/Http/src/Internal/ReferenceReadStream.cs154
-rw-r--r--src/Http/Http/src/Internal/RequestCookieCollection.cs232
-rw-r--r--src/Http/Http/src/Internal/ResponseCookies.cs139
-rw-r--r--src/Http/Http/src/Microsoft.AspNetCore.Http.csproj21
-rw-r--r--src/Http/Http/src/MiddlewareFactory.cs35
-rw-r--r--src/Http/Http/src/RequestFormReaderExtensions.cs48
-rw-r--r--src/Http/Http/src/baseline.netcore.json2783
-rw-r--r--src/Http/Http/test/Authentication/DefaultAuthenticationManagerTests.cs104
-rw-r--r--src/Http/Http/test/DefaultHttpContextTests.cs352
-rw-r--r--src/Http/Http/test/Features/FakeResponseFeature.cs29
-rw-r--r--src/Http/Http/test/Features/FormFeatureTests.cs521
-rw-r--r--src/Http/Http/test/Features/HttpRequestIdentifierFeatureTests.cs43
-rw-r--r--src/Http/Http/test/Features/NonSeekableReadStream.cs72
-rw-r--r--src/Http/Http/test/Features/QueryFeatureTests.cs67
-rw-r--r--src/Http/Http/test/HeaderDictionaryTests.cs107
-rw-r--r--src/Http/Http/test/HttpContextFactoryTests.cs39
-rw-r--r--src/Http/Http/test/HttpServiceCollectionExtensionsTests.cs33
-rw-r--r--src/Http/Http/test/Internal/ApplicationBuilderTests.cs35
-rw-r--r--src/Http/Http/test/Internal/BindingAddressTests.cs70
-rw-r--r--src/Http/Http/test/Internal/BufferingHelperTests.cs19
-rw-r--r--src/Http/Http/test/Internal/DefaultHttpRequestTests.cs235
-rw-r--r--src/Http/Http/test/Internal/DefaultHttpResponseTests.cs90
-rw-r--r--src/Http/Http/test/Microsoft.AspNetCore.Http.Tests.csproj12
-rw-r--r--src/Http/Http/test/RequestCookiesCollectionTests.cs43
-rw-r--r--src/Http/Http/test/ResponseCookiesTest.cs124
-rw-r--r--src/Http/HttpAbstractions.sln312
-rw-r--r--src/Http/Owin/src/DictionaryStringArrayWrapper.cs81
-rw-r--r--src/Http/Owin/src/DictionaryStringValuesWrapper.cs126
-rw-r--r--src/Http/Owin/src/IOwinEnvironmentFeature.cs12
-rw-r--r--src/Http/Owin/src/Microsoft.AspNetCore.Owin.csproj15
-rw-r--r--src/Http/Owin/src/OwinConstants.cs177
-rw-r--r--src/Http/Owin/src/OwinEnvironment.cs397
-rw-r--r--src/Http/Owin/src/OwinEnvironmentFeature.cs12
-rw-r--r--src/Http/Owin/src/OwinExtensions.cs175
-rw-r--r--src/Http/Owin/src/OwinFeatureCollection.cs412
-rw-r--r--src/Http/Owin/src/Utilities.cs69
-rw-r--r--src/Http/Owin/src/WebSockets/OwinWebSocketAcceptAdapter.cs143
-rw-r--r--src/Http/Owin/src/WebSockets/OwinWebSocketAcceptContext.cs48
-rw-r--r--src/Http/Owin/src/WebSockets/OwinWebSocketAdapter.cs200
-rw-r--r--src/Http/Owin/src/WebSockets/WebSocketAcceptAdapter.cs92
-rw-r--r--src/Http/Owin/src/WebSockets/WebSocketAdapter.cs171
-rw-r--r--src/Http/Owin/src/baseline.netcore.json1010
-rw-r--r--src/Http/Owin/test/Microsoft.AspNetCore.Owin.Tests.csproj13
-rw-r--r--src/Http/Owin/test/OwinEnvironmentTests.cs148
-rw-r--r--src/Http/Owin/test/OwinExtensionTests.cs164
-rw-r--r--src/Http/Owin/test/OwinFeatureCollectionTests.cs68
-rw-r--r--src/Http/README.md6
-rw-r--r--src/Http/WebUtilities/src/Base64UrlTextEncoder.cs30
-rw-r--r--src/Http/WebUtilities/src/BufferedReadStream.cs431
-rw-r--r--src/Http/WebUtilities/src/FileBufferingReadStream.cs354
-rw-r--r--src/Http/WebUtilities/src/FileMultipartSection.cs70
-rw-r--r--src/Http/WebUtilities/src/FormMultipartSection.cs63
-rw-r--r--src/Http/WebUtilities/src/FormReader.cs312
-rw-r--r--src/Http/WebUtilities/src/HttpRequestStreamReader.cs374
-rw-r--r--src/Http/WebUtilities/src/HttpResponseStreamWriter.cs340
-rw-r--r--src/Http/WebUtilities/src/KeyValueAccumulator.cs86
-rw-r--r--src/Http/WebUtilities/src/Microsoft.AspNetCore.WebUtilities.csproj18
-rw-r--r--src/Http/WebUtilities/src/MultipartBoundary.cs72
-rw-r--r--src/Http/WebUtilities/src/MultipartReader.cs118
-rw-r--r--src/Http/WebUtilities/src/MultipartReaderStream.cs336
-rw-r--r--src/Http/WebUtilities/src/MultipartSection.cs48
-rw-r--r--src/Http/WebUtilities/src/MultipartSectionConverterExtensions.cs74
-rw-r--r--src/Http/WebUtilities/src/MultipartSectionStreamExtensions.cs49
-rw-r--r--src/Http/WebUtilities/src/Properties/AssemblyInfo.cs6
-rw-r--r--src/Http/WebUtilities/src/QueryHelpers.cs191
-rw-r--r--src/Http/WebUtilities/src/ReasonPhrases.cs87
-rw-r--r--src/Http/WebUtilities/src/Resources.Designer.cs89
-rw-r--r--src/Http/WebUtilities/src/Resources.resx129
-rw-r--r--src/Http/WebUtilities/src/StreamHelperExtensions.cs51
-rw-r--r--src/Http/WebUtilities/src/baseline.netcore.json2272
-rw-r--r--src/Http/WebUtilities/test/FileBufferingReadStreamTests.cs299
-rw-r--r--src/Http/WebUtilities/test/FormReaderAsyncTest.cs22
-rw-r--r--src/Http/WebUtilities/test/FormReaderTests.cs230
-rw-r--r--src/Http/WebUtilities/test/HttpRequestStreamReaderTest.cs313
-rw-r--r--src/Http/WebUtilities/test/HttpResponseStreamWriterTest.cs574
-rw-r--r--src/Http/WebUtilities/test/Microsoft.AspNetCore.WebUtilities.Tests.csproj11
-rw-r--r--src/Http/WebUtilities/test/MultipartReaderTests.cs383
-rw-r--r--src/Http/WebUtilities/test/NonSeekableReadStream.cs74
-rw-r--r--src/Http/WebUtilities/test/QueryHelpersTests.cs114
-rw-r--r--src/Http/WebUtilities/test/WebEncodersTests.cs65
-rw-r--r--src/Http/samples/SampleApp/PooledHttpContext.cs54
-rw-r--r--src/Http/samples/SampleApp/PooledHttpContextFactory.cs83
-rw-r--r--src/Http/samples/SampleApp/Program.cs21
-rw-r--r--src/Http/samples/SampleApp/SampleApp.csproj13
-rw-r--r--src/Shared/Hosting.WebHostBuilderFactory/FactoryResolutionResult.cs54
-rw-r--r--src/Shared/Hosting.WebHostBuilderFactory/FactoryResolutionResultKind.cs14
-rw-r--r--src/Shared/Hosting.WebHostBuilderFactory/WebHostFactoryResolver.cs68
-rw-r--r--src/Templating/.gitignore (renamed from src/templating/.gitignore)0
-rw-r--r--src/Templating/Directory.Build.props17
-rw-r--r--src/Templating/Directory.Build.targets (renamed from src/templating/Directory.Build.targets)0
-rw-r--r--src/Templating/NuGetPackageVerifier.json (renamed from src/templating/NuGetPackageVerifier.json)0
-rw-r--r--src/Templating/README.md (renamed from src/templating/README.md)0
-rw-r--r--src/Templating/Templating.sln (renamed from src/templating/Templating.sln)0
-rw-r--r--src/Templating/build/dependencies.props (renamed from src/templating/build/dependencies.props)0
-rw-r--r--src/Templating/build/repo.props (renamed from src/templating/build/repo.props)0
-rw-r--r--src/Templating/build/sources.props (renamed from src/templating/build/sources.props)0
-rw-r--r--src/Templating/src/Directory.Build.props (renamed from src/templating/src/Directory.Build.props)0
-rw-r--r--src/Templating/src/Directory.Build.targets (renamed from src/templating/src/Directory.Build.targets)0
-rw-r--r--src/Templating/src/GenerateContent.targets (renamed from src/templating/src/GenerateContent.targets)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/Microsoft.DotNet.Web.Client.ItemTemplates.csproj (renamed from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/Microsoft.DotNet.Web.Client.ItemTemplates.csproj)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/.template.config/dotnetcli.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/.template.config/dotnetcli.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/.template.config/template.json (renamed from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/.template.config/template.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/styleSheet1.less (renamed from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Less/styleSheet1.less)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/.template.config/dotnetcli.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/.template.config/dotnetcli.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/.template.config/template.json (renamed from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/.template.config/template.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/styleSheet1.scss (renamed from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/Scss/styleSheet1.scss)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/.template.config/dotnetcli.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/.template.config/dotnetcli.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/.template.config/template.json (renamed from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/.template.config/template.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/file1.ts (renamed from src/templating/src/Microsoft.DotNet.Web.Client.ItemTemplates/content/TypeScript/file1.ts)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/Microsoft.DotNet.Web.ItemTemplates.csproj (renamed from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/Microsoft.DotNet.Web.ItemTemplates.csproj)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/.template.config/dotnetcli.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/.template.config/dotnetcli.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/.template.config/template.json (renamed from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/.template.config/template.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/Index.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/Index.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/Index.cshtml.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/Index.cshtml.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/.template.config/dotnetcli.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/.template.config/dotnetcli.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/.template.config/template.json (renamed from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/.template.config/template.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/_ViewImports.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/_ViewImports.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/.template.config/dotnetcli.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/.template.config/dotnetcli.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/.template.config/template.json (renamed from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/.template.config/template.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/_ViewStart.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/_ViewStart.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/.gitignore (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/.gitignore)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/EmptyWeb-CSharp.csproj.in (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/EmptyWeb-CSharp.csproj.in)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/EmptyWeb-FSharp.fsproj.in (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/EmptyWeb-FSharp.fsproj.in)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/Microsoft.DotNet.Web.ProjectTemplates.csproj (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/Microsoft.DotNet.Web.ProjectTemplates.csproj)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/RazorClassLibrary-CSharp.csproj.in (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/RazorClassLibrary-CSharp.csproj.in)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/StarterWeb-CSharp.csproj.in (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/StarterWeb-CSharp.csproj.in)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/StarterWeb-FSharp.fsproj.in (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/StarterWeb-FSharp.fsproj.in)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/WebApi-CSharp.csproj.in (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/WebApi-CSharp.csproj.in)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/WebApi-FSharp.fsproj.in (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/WebApi-FSharp.fsproj.in)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/Directory.Build.props (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/Directory.Build.props)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/Directory.Build.targets (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/Directory.Build.targets)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/dotnetcli.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/dotnetcli.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/template.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/vs-2017.3.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/vs-2017.3.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/vs-2017.3/Empty.png (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/.template.config/vs-2017.3/Empty.png)bin303 -> 303 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Program.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Program.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Properties/launchSettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Properties/launchSettings.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Startup.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/Startup.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/app.config (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/app.config)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/wwwroot/-.- (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/wwwroot/-.-)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/dotnetcli.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/dotnetcli.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/template.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/template.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/vs-2017.3.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/vs-2017.3.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/vs-2017.3/Empty.png (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/.template.config/vs-2017.3/Empty.png)bin303 -> 303 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Program.fs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Program.fs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Properties/launchSettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Properties/launchSettings.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Startup.fs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/Startup.fs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/app.config (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-FSharp/app.config)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/dotnetcli.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/dotnetcli.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/template.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/template.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/vs-2017.3.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/vs-2017.3.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/vs-2017.3/RazorClassLibrary.ico (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/.template.config/vs-2017.3/RazorClassLibrary.ico)bin42890 -> 42890 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/Areas/MyFeature/Pages/Page1.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/Areas/MyFeature/Pages/Page1.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/Areas/MyFeature/Pages/Page1.cshtml.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorClassLibrary-CSharp/Areas/MyFeature/Pages/Page1.cshtml.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/dotnetcli.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/dotnetcli.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/template.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/vs-2017.3.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/vs-2017.3.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/vs-2017.3/WebApplication.png (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/.template.config/vs-2017.3/WebApplication.png)bin1196 -> 1196 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Areas/Identity/Pages/_ViewStart.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Areas/Identity/Pages/_ViewStart.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/ApplicationDbContext.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/ApplicationDbContext.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/ApplicationDbContextModelSnapshot.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlLite/ApplicationDbContextModelSnapshot.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/ApplicationDbContextModelSnapshot.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Data/SqlServer/ApplicationDbContextModelSnapshot.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/About.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/About.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/About.cshtml.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/About.cshtml.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Contact.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Contact.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Contact.cshtml.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Contact.cshtml.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Error.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Error.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Error.cshtml.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Error.cshtml.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Index.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Index.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Index.cshtml.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Index.cshtml.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Privacy.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Privacy.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Privacy.cshtml.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Privacy.cshtml.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_CookieConsentPartial.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_CookieConsentPartial.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_Layout.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_Layout.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_LoginPartial.Identity.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_LoginPartial.Identity.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_LoginPartial.OrgAuth.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_LoginPartial.OrgAuth.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_ValidationScriptsPartial.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/Shared/_ValidationScriptsPartial.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/_ViewImports.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/_ViewImports.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/_ViewStart.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Pages/_ViewStart.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Program.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Program.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Properties/launchSettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Properties/launchSettings.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Startup.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/Startup.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/app.config (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/app.config)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/app.db (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/app.db)bin106496 -> 106496 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/appsettings.Development.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/appsettings.Development.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/appsettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/appsettings.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/css/site.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/css/site.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/css/site.min.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/css/site.min.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/favicon.ico (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/favicon.ico)bin32038 -> 32038 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner1.svg (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner1.svg)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner2.svg (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner2.svg)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner3.svg (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/images/banner3.svg)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/js/site.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/.bower.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/.bower.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/LICENSE (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/LICENSE)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot)bin20127 -> 20127 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf)bin45404 -> 45404 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff)bin23424 -> 23424 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2)bin18028 -> 18028 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/npm.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/bootstrap/dist/js/npm.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/.bower.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/.bower.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/LICENSE.md (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/LICENSE.md)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/.bower.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/.bower.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/LICENSE.txt (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/LICENSE.txt)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.map (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/RazorPagesWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.map)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/dotnetcli.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/dotnetcli.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/template.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/vs-2017.3.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/vs-2017.3.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/vs-2017.3/WebApplication.png (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/.template.config/vs-2017.3/WebApplication.png)bin1196 -> 1196 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Areas/Identity/Pages/_ViewStart.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Areas/Identity/Pages/_ViewStart.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Controllers/HomeController.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Controllers/HomeController.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/ApplicationDbContext.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/ApplicationDbContext.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.Designer.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/00000000000000_CreateIdentitySchema.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/ApplicationDbContextModelSnapshot.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlLite/ApplicationDbContextModelSnapshot.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.Designer.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/00000000000000_CreateIdentitySchema.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/ApplicationDbContextModelSnapshot.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Data/SqlServer/ApplicationDbContextModelSnapshot.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Models/ErrorViewModel.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Models/ErrorViewModel.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Program.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Program.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Properties/launchSettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Properties/launchSettings.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Startup.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Startup.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/About.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/About.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Contact.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Contact.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Index.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Index.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Privacy.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Home/Privacy.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/Error.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/Error.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_CookieConsentPartial.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_CookieConsentPartial.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_Layout.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_Layout.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_LoginPartial.Identity.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_LoginPartial.Identity.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_LoginPartial.OrgAuth.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_LoginPartial.OrgAuth.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_ValidationScriptsPartial.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/Shared/_ValidationScriptsPartial.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/_ViewImports.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/_ViewImports.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/_ViewStart.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/Views/_ViewStart.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/app.config (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/app.config)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/app.db (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/app.db)bin106496 -> 106496 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/appsettings.Development.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/appsettings.Development.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/appsettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/appsettings.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/css/site.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/css/site.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/css/site.min.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/css/site.min.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/favicon.ico (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/favicon.ico)bin32038 -> 32038 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner1.svg (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner1.svg)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner2.svg (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner2.svg)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner3.svg (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/images/banner3.svg)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/js/site.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/js/site.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/js/site.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/js/site.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/.bower.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/.bower.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/LICENSE (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/LICENSE)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot)bin20127 -> 20127 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf)bin45404 -> 45404 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff)bin23424 -> 23424 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2)bin18028 -> 18028 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/npm.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/bootstrap/dist/js/npm.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/.bower.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/.bower.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/LICENSE.md (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/LICENSE.md)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/.bower.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/.bower.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/LICENSE.txt (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/LICENSE.txt)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.map (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-CSharp/wwwroot/lib/jquery/dist/jquery.min.map)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.bowerrc (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.bowerrc)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/dotnetcli.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/dotnetcli.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/template.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.template.config/template.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Controllers/HomeController.fs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Controllers/HomeController.fs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Models/ErrorViewModel.fs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Models/ErrorViewModel.fs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Program.fs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Program.fs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Properties/launchSettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Properties/launchSettings.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Startup.fs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Startup.fs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/About.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/About.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/Contact.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/Contact.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/Index.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Home/Index.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/Error.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/Error.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_Layout.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_Layout.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_ValidationScriptsPartial.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/Shared/_ValidationScriptsPartial.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/_ViewImports.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/_ViewImports.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/_ViewStart.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/Views/_ViewStart.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/app.config (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/app.config)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/appsettings.Development.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/appsettings.Development.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/appsettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/appsettings.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/css/site.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/css/site.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/css/site.min.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/css/site.min.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/favicon.ico (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/favicon.ico)bin32038 -> 32038 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner1.svg (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner1.svg)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner2.svg (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner2.svg)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner3.svg (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/images/banner3.svg)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/js/site.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/.bower.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/.bower.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/LICENSE (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/LICENSE)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.css.map)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap-theme.min.css.map)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.eot)bin20127 -> 20127 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.svg)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.ttf)bin45404 -> 45404 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff)bin23424 -> 23424 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2 (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/fonts/glyphicons-halflings-regular.woff2)bin18028 -> 18028 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/npm.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/bootstrap/dist/js/npm.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/.bower.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/.bower.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/.bower.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/LICENSE.md (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/LICENSE.md)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/additional-methods.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/additional-methods.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/.bower.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/.bower.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/LICENSE.txt (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/LICENSE.txt)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.min.js (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.min.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.min.map (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/wwwroot/lib/jquery/dist/jquery.min.map)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/dotnetcli.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/template.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/vs-2017.3.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/vs-2017.3.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/vs-2017.3/WebAPI.png (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/.template.config/vs-2017.3/WebAPI.png)bin523 -> 523 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Controllers/ValuesController.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Controllers/ValuesController.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Program.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Program.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Properties/launchSettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Properties/launchSettings.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Startup.cs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/Startup.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/app.config (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/app.config)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/appsettings.Development.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/appsettings.Development.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/appsettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/appsettings.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/wwwroot/-.- (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/wwwroot/-.-)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/dotnetcli.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/dotnetcli.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/template.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/template.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/vs-2017.3.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/vs-2017.3.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/vs-2017.3/WebAPI.png (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/.template.config/vs-2017.3/WebAPI.png)bin523 -> 523 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Controllers/ValuesController.fs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Controllers/ValuesController.fs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Program.fs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Program.fs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Properties/launchSettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Properties/launchSettings.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Startup.fs (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/Startup.fs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/app.config (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/app.config)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/appsettings.Development.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/appsettings.Development.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/appsettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/appsettings.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/wwwroot/-.- (renamed from src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/wwwroot/-.-)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/.gitignore (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/.gitignore)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/Angular-CSharp.csproj.in (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/Angular-CSharp.csproj.in)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/Microsoft.DotNet.Web.Spa.ProjectTemplates.csproj)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/React-CSharp.csproj.in (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/React-CSharp.csproj.in)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/ReactRedux-CSharp.csproj.in (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/ReactRedux-CSharp.csproj.in)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.gitignore (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.gitignore)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/dotnetcli.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/dotnetcli.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/icon.png (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/icon.png)bin1833 -> 1833 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/template.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/template.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/vs-2017.3.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/.template.config/vs-2017.3.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.angular-cli.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.angular-cli.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.editorconfig (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.editorconfig)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.gitignore (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/.gitignore)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/README.md (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/README.md)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/app.e2e-spec.ts (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/app.e2e-spec.ts)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/app.po.ts (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/app.po.ts)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/tsconfig.e2e.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/e2e/tsconfig.e2e.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/karma.conf.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/karma.conf.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/package-lock.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/package-lock.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/package.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/package.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/protractor.conf.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/protractor.conf.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.css (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.html (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.html)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.ts (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.component.ts)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.module.ts (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/app.module.ts)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.html (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.html)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.spec.ts (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.spec.ts)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.ts (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/counter/counter.component.ts)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/fetch-data/fetch-data.component.html (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/fetch-data/fetch-data.component.html)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/fetch-data/fetch-data.component.ts (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/fetch-data/fetch-data.component.ts)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/home/home.component.html (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/home/home.component.html)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/home/home.component.ts (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/home/home.component.ts)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.css (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.html (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.html)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.ts (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/app/nav-menu/nav-menu.component.ts)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/assets/.gitkeep (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/assets/.gitkeep)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/environments/environment.prod.ts (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/environments/environment.prod.ts)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/environments/environment.ts (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/environments/environment.ts)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/index.html (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/index.html)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/main.ts (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/main.ts)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/polyfills.ts (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/polyfills.ts)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/styles.css (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/styles.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/test.ts (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/test.ts)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/tsconfig.app.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/tsconfig.app.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/tsconfig.spec.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/tsconfig.spec.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/typings.d.ts (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/src/typings.d.ts)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/tsconfig.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/tsconfig.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/tslint.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/ClientApp/tslint.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/SampleDataController.cs (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Controllers/SampleDataController.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/Error.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/Error.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/Error.cshtml.cs (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/Error.cshtml.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/_ViewImports.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Pages/_ViewImports.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Program.cs (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Program.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Properties/launchSettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Properties/launchSettings.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Startup.cs (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/Startup.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/app.config (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/app.config)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/appsettings.Development.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/appsettings.Development.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/appsettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/appsettings.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/wwwroot/favicon.ico (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Angular-CSharp/wwwroot/favicon.ico)bin32038 -> 32038 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Directory.Build.props (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Directory.Build.props)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Directory.Build.targets (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/Directory.Build.targets)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.gitignore (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.gitignore)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/dotnetcli.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/dotnetcli.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/icon.png (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/icon.png)bin2431 -> 2431 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/template.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/template.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/vs-2017.3.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/.template.config/vs-2017.3.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/.gitignore (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/.gitignore)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/README.md (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/README.md)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/package-lock.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/package-lock.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/package.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/package.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/favicon.ico (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/favicon.ico)bin32038 -> 32038 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/index.html (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/index.html)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/manifest.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/public/manifest.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/App.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/App.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/App.test.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/App.test.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Counter.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Counter.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/FetchData.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/FetchData.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Home.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Home.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Layout.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/Layout.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/NavMenu.css (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/NavMenu.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/NavMenu.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/components/NavMenu.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/index.css (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/index.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/index.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/index.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/registerServiceWorker.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/ClientApp/src/registerServiceWorker.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Controllers/SampleDataController.cs (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Controllers/SampleDataController.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/Error.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/Error.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/Error.cshtml.cs (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/Error.cshtml.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/_ViewImports.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Pages/_ViewImports.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Program.cs (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Program.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Properties/launchSettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Properties/launchSettings.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Startup.cs (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/Startup.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/app.config (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/app.config)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/appsettings.Development.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/appsettings.Development.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/appsettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/React-CSharp/appsettings.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.gitignore (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.gitignore)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/dotnetcli.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/dotnetcli.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/icon.png (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/icon.png)bin2876 -> 2876 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/template.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/template.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/vs-2017.3.host.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/.template.config/vs-2017.3.host.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.gitignore (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/.gitignore)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/README.md (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/README.md)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/package-lock.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/package-lock.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/package.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/package.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/favicon.ico (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/favicon.ico)bin32038 -> 32038 bytes
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/index.html (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/index.html)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/manifest.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/public/manifest.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/App.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/App.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/App.test.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/App.test.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Counter.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Counter.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/FetchData.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/FetchData.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Home.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Home.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Layout.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/Layout.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/NavMenu.css (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/NavMenu.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/NavMenu.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/components/NavMenu.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/index.css (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/index.css)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/index.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/index.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/registerServiceWorker.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/registerServiceWorker.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/Counter.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/Counter.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/WeatherForecasts.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/WeatherForecasts.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/configureStore.js (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/ClientApp/src/store/configureStore.js)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Controllers/SampleDataController.cs (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Controllers/SampleDataController.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/Error.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/Error.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/Error.cshtml.cs (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/Error.cshtml.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/_ViewImports.cshtml (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Pages/_ViewImports.cshtml)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Program.cs (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Program.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Properties/launchSettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Properties/launchSettings.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Startup.cs (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/Startup.cs)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/app.config (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/app.config)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/appsettings.Development.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/appsettings.Development.json)0
-rw-r--r--src/Templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/appsettings.json (renamed from src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/content/ReactRedux-CSharp/appsettings.json)0
-rw-r--r--src/Templating/src/SetPackageProperties.targets (renamed from src/templating/src/SetPackageProperties.targets)0
-rw-r--r--src/Templating/src/THIRD-PARTY-NOTICES (renamed from src/templating/src/THIRD-PARTY-NOTICES)0
-rw-r--r--src/Templating/src/templates.nuspec (renamed from src/templating/src/templates.nuspec)0
-rw-r--r--src/Templating/test/Directory.Build.targets (renamed from src/templating/test/Directory.Build.targets)0
-rw-r--r--src/Templating/test/DotNetToolsInstaller/DotNetToolsInstaller.csproj (renamed from src/templating/test/DotNetToolsInstaller/DotNetToolsInstaller.csproj)0
-rw-r--r--src/Templating/test/GenerateTestProps.targets (renamed from src/templating/test/GenerateTestProps.targets)0
-rw-r--r--src/Templating/test/TemplateTests.props.in (renamed from src/templating/test/TemplateTests.props.in)0
-rw-r--r--src/Templating/test/Templates.Test/.gitattributes (renamed from src/templating/test/Templates.Test/.gitattributes)0
-rw-r--r--src/Templating/test/Templates.Test/BaselineTest.cs (renamed from src/templating/test/Templates.Test/BaselineTest.cs)0
-rw-r--r--src/Templating/test/Templates.Test/ByteOrderMarkTest.cs (renamed from src/templating/test/Templates.Test/ByteOrderMarkTest.cs)0
-rw-r--r--src/Templating/test/Templates.Test/CdnScriptTagTests.cs (renamed from src/templating/test/Templates.Test/CdnScriptTagTests.cs)0
-rw-r--r--src/Templating/test/Templates.Test/EmptyWebTemplateTest.cs (renamed from src/templating/test/Templates.Test/EmptyWebTemplateTest.cs)0
-rw-r--r--src/Templating/test/Templates.Test/Helpers/AddFirewallExclusion.cs (renamed from src/templating/test/Templates.Test/Helpers/AddFirewallExclusion.cs)0
-rw-r--r--src/Templating/test/Templates.Test/Helpers/AspNetProcess.cs (renamed from src/templating/test/Templates.Test/Helpers/AspNetProcess.cs)0
-rw-r--r--src/Templating/test/Templates.Test/Helpers/Npm.cs (renamed from src/templating/test/Templates.Test/Helpers/Npm.cs)0
-rw-r--r--src/Templating/test/Templates.Test/Helpers/ProcessEx.cs (renamed from src/templating/test/Templates.Test/Helpers/ProcessEx.cs)0
-rw-r--r--src/Templating/test/Templates.Test/Helpers/TemplatePackageInstaller.cs (renamed from src/templating/test/Templates.Test/Helpers/TemplatePackageInstaller.cs)0
-rw-r--r--src/Templating/test/Templates.Test/Helpers/TemplateTestBase.cs (renamed from src/templating/test/Templates.Test/Helpers/TemplateTestBase.cs)0
-rw-r--r--src/Templating/test/Templates.Test/Helpers/WebDriverExtensions.cs (renamed from src/templating/test/Templates.Test/Helpers/WebDriverExtensions.cs)0
-rw-r--r--src/Templating/test/Templates.Test/Helpers/WebDriverFactory.cs (renamed from src/templating/test/Templates.Test/Helpers/WebDriverFactory.cs)0
-rw-r--r--src/Templating/test/Templates.Test/MvcTemplateTest.cs (renamed from src/templating/test/Templates.Test/MvcTemplateTest.cs)0
-rw-r--r--src/Templating/test/Templates.Test/RazorPagesTemplateTest.cs (renamed from src/templating/test/Templates.Test/RazorPagesTemplateTest.cs)0
-rw-r--r--src/Templating/test/Templates.Test/SpaTemplateTest/AngularTemplateTest.cs (renamed from src/templating/test/Templates.Test/SpaTemplateTest/AngularTemplateTest.cs)0
-rw-r--r--src/Templating/test/Templates.Test/SpaTemplateTest/ReactReduxTemplateTest.cs (renamed from src/templating/test/Templates.Test/SpaTemplateTest/ReactReduxTemplateTest.cs)0
-rw-r--r--src/Templating/test/Templates.Test/SpaTemplateTest/ReactTemplateTest.cs (renamed from src/templating/test/Templates.Test/SpaTemplateTest/ReactTemplateTest.cs)0
-rw-r--r--src/Templating/test/Templates.Test/SpaTemplateTest/SpaTemplateTestBase.cs (renamed from src/templating/test/Templates.Test/SpaTemplateTest/SpaTemplateTestBase.cs)0
-rw-r--r--src/Templating/test/Templates.Test/Templates.Test.csproj (renamed from src/templating/test/Templates.Test/Templates.Test.csproj)0
-rw-r--r--src/Templating/test/Templates.Test/WebApiTemplateTest.cs (renamed from src/templating/test/Templates.Test/WebApiTemplateTest.cs)0
-rw-r--r--src/Templating/test/Templates.Test/template-baselines.json (renamed from src/templating/test/Templates.Test/template-baselines.json)0
-rw-r--r--src/Templating/version.props (renamed from src/templating/version.props)0
-rw-r--r--src/templating/Directory.Build.props17
1013 files changed, 84332 insertions, 78 deletions
diff --git a/.gitmodules b/.gitmodules
index be6b9310a7..b64f1a6afc 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 99d625f1ad..1999dba76d 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 717198331b..587242f9e5 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 d5b5dab276..58a3f1f766 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 b46e6e7a6c..8b1e4119f1 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 32846302dd..684b4db40b 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 1d5261431b..a1b4241fbb 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 ccc78ab2e8..638753659f 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 a8f9316a92..9d9a2241b2 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 c2792fb384..0ad0e06111 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 7d04ed99b8..0e25409b66 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 a96067429f..f319c9348c 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 823c131a30..5a3e9661b9 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
-Subproject 3f7ee338d4cdd1c49bb965338ad7b118fa070a8
diff --git a/modules/HttpAbstractions b/modules/HttpAbstractions
deleted file mode 160000
-Subproject d142d58eb43626961117136c51993d51dfb7371
diff --git a/src/Hosting/Abstractions/src/EnvironmentName.cs b/src/Hosting/Abstractions/src/EnvironmentName.cs
new file mode 100644
index 0000000000..d5522d1124
--- /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 0000000000..f61b86eb86
--- /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 0000000000..ad3269859d
--- /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 0000000000..cb028c327b
--- /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 0000000000..f4613dd7d9
--- /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 0000000000..5feeb38eb7
--- /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 0000000000..e65ed18fb6
--- /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 0000000000..3a533c8df2
--- /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 0000000000..2f0a3cf39d
--- /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 0000000000..97331e4768
--- /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 0000000000..2cf3bc1163
--- /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 0000000000..e58ac19774
--- /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 0000000000..ad203bcedb
--- /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 0000000000..a01be8ea3f
--- /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 0000000000..58e8d0798b
--- /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 0000000000..4de391d0a2
--- /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 0000000000..7536bf1233
--- /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 0000000000..e188c0b7fd
--- /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 0000000000..d44398fb69
--- /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 0000000000..958f8b5dcc
--- /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 0000000000..b75958fa52
--- /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 0000000000..37b715c5b0
--- /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 0000000000..ed8d0fd06e
--- /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 0000000000..4206d0d62a
--- /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 0000000000..ee6fbcfad8
--- /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 0000000000..cd3e2d9fb2
--- /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 0000000000..d485b1a060
--- /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 0000000000..1f8d1887d7
--- /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 0000000000..c12d0bcdd6
--- /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 0000000000..199f8aae85
--- /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 0000000000..a0579880a0
--- /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 0000000000..ab440481ba
--- /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 0000000000..7506028a3c
--- /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 0000000000..f7d8f61933
--- /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 0000000000..9fcb01aa12
--- /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 0000000000..a57df9bcbc
--- /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 0000000000..48b8758181
--- /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 0000000000..d7211d39d9
--- /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 0000000000..f854c85946
--- /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 0000000000..3764427f63
--- /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 0000000000..e9e611bc69
--- /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 0000000000..49635699d1
--- /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 0000000000..627b36bbc2
--- /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 0000000000..39703dc79d
--- /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 0000000000..088072729c
--- /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 0000000000..64d6fe523d
--- /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 0000000000..098ec8cdb0
--- /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 0000000000..b31f9478d1
--- /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 0000000000..d354ad946e
--- /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 0000000000..52a6db45d3
--- /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 0000000000..8f8360c565
--- /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 0000000000..4d3287c12d
--- /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 0000000000..3925cfd2f2
--- /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 0000000000..b0a9f0354a
--- /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 0000000000..5a180ba9b4
--- /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 0000000000..423b898cec
--- /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 0000000000..09c7e6d96b
--- /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 0000000000..06a3e00cf8
--- /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 0000000000..ca85990914
--- /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 0000000000..c6b24c57e8
--- /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 0000000000..012e05bee7
--- /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 0000000000..fe54861d87
--- /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 0000000000..39a83d8754
--- /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 0000000000..cea4235c8c
--- /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 0000000000..9fa7cf2151
--- /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 0000000000..c4ffaa799d
--- /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 0000000000..3bf3e6ce38
--- /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 0000000000..5b78e046e1
--- /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 0000000000..2cc7a00701
--- /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 0000000000..77c53e596b
--- /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 0000000000..73fca3bab1
--- /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 0000000000..0225a6789f
--- /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 0000000000..9387305999
--- /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 0000000000..8e345a1020
--- /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 0000000000..5090051fd6
--- /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 0000000000..2abe4a4b22
--- /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 0000000000..82dd2c7cb6
--- /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 0000000000..2b629896bc
--- /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 0000000000..0c85ad5413
--- /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 0000000000..895fa654e9
--- /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 0000000000..1d9fa8ef37
--- /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 0000000000..b7c1f223d3
--- /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 0000000000..93e054fbc6
--- /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 0000000000..e421ba08c7
--- /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 0000000000..c9164fa98f
--- /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 0000000000..b3cbd60225
--- /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 0000000000..e7c1be78f9
--- /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 0000000000..ce4132ac13
--- /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 0000000000..4c397fe8bd
--- /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 0000000000..bff10f9442
--- /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 0000000000..c4a5776502
--- /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 0000000000..ad596e2546
--- /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 0000000000..9390f4727f
--- /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 0000000000..985f920473
--- /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 0000000000..7056b37d68
--- /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 0000000000..de1dc01899
--- /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 0000000000..3a646b005b
--- /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 0000000000..32b51d8a95
--- /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 0000000000..ca8e25ed9e
--- /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 0000000000..058abc894a
--- /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 0000000000..5fbffaad1b
--- /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 0000000000..06a1ad614b
--- /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 0000000000..151a66777a
--- /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 0000000000..c153af4dc1
--- /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 0000000000..75d5ccb98c
--- /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 0000000000..c1244e5c8f
--- /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 0000000000..88a43a4319
--- /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 0000000000..0bc568277c
--- /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 0000000000..d5669ad838
--- /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 0000000000..d3415c9f70
--- /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 0000000000..24a9a267a5
--- /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 0000000000..cc9d173da6
--- /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 0000000000..b04e5a0d47
--- /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 0000000000..13896a3b2d
--- /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 0000000000..30460913bd
--- /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 0000000000..02d8715984
--- /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 0000000000..3d824741c3
--- /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 0000000000..6aed1f4d64
--- /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 0000000000..1eb1aea8c0
--- /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 0000000000..a1dc7e24db
--- /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 0000000000..8d7d20bc1e
--- /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 0000000000..75ac9f6f41
--- /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 0000000000..1e9c868b4b
--- /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 0000000000..510c713f59
--- /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 0000000000..060c8ed0ca
--- /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 0000000000..7ebcb30367
--- /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 0000000000..3c81f32902
--- /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 0000000000..eb66761807
--- /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 0000000000..400ae978ed
--- /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 0000000000..bc7aecb700
--- /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 0000000000..1bc6c4b766
--- /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 0000000000..e5c54d21e8
--- /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 0000000000..a102cd02da
--- /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 0000000000..61d3a49325
--- /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 0000000000..0bcfea647c
--- /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 0000000000..9b66f883c2
--- /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 0000000000..12f2b83de1
--- /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 0000000000..e693a22278
--- /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 0000000000..9e26dfeeb6
--- /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 0000000000..8d54d6ac70
--- /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 0000000000..554cc63eda
--- /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 0000000000..2109809d1a
--- /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 0000000000..6886b1aac4
--- /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 0000000000..acb65b2006
--- /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 0000000000..5e17a096e7
--- /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 0000000000..c770ec75ba
--- /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 0000000000..d634f2dbe2
--- /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 0000000000..c6c7b47e18
--- /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 0000000000..0cd3459a80
--- /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 0000000000..398a575d9d
--- /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 0000000000..d1a77e5af6
--- /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 0000000000..cccf19cdd5
--- /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 0000000000..3a0b0a552e
--- /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 0000000000..e3deb670a5
--- /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 0000000000..85f67361dd
--- /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 0000000000..73f1c86d29
--- /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 0000000000..21539c8988
--- /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 0000000000..51041799c4
--- /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 0000000000..2a37015aae
--- /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 0000000000..f8af4cf64d
--- /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 0000000000..3101c2965f
--- /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 0000000000..b1eee1fd94
--- /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 0000000000..0c4112a61c
--- /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 0000000000..f468d05fe3
--- /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 0000000000..9e657fbe3e
--- /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 0000000000..a37e8c99ef
--- /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 0000000000..9850e83f45
--- /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 0000000000..bb1d669846
--- /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 0000000000..74f18791c8
--- /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 0000000000..4879031f56
--- /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 0000000000..bf5567d81a
--- /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 0000000000..1ac316178f
--- /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 0000000000..123dcf8790
--- /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 0000000000..ebdfdb383e
--- /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 0000000000..9c881e56e3
--- /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 0000000000..65a226f3cd
--- /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 0000000000..8413c47c90
--- /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 0000000000..68ec11c9b0
--- /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 0000000000..272e747446
--- /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 0000000000..88a77cfc6c
--- /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 0000000000..381d621a10
--- /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 0000000000..3daae1c597
--- /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 0000000000..a82d7bc1e5
--- /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 0000000000..1c229e96b8
--- /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 0000000000..4a56c432be
--- /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 0000000000..95412eec30
--- /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 0000000000..f2732f75c4
--- /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 0000000000..40bd0c87a3
--- /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 0000000000..b25cb703ad
--- /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 0000000000..0b52bc36b8
--- /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 0000000000..40bd0c87a3
--- /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 0000000000..a5a20508b9
--- /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 0000000000..10ecf07a99
--- /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 0000000000..40bd0c87a3
--- /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 0000000000..9322836fe0
--- /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 0000000000..2655a7e937
--- /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 0000000000..40bd0c87a3
--- /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 0000000000..405ec1fba0
--- /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 0000000000..9f4e27223c
--- /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 0000000000..7b87b0eed5
--- /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 0000000000..36056bff48
--- /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 0000000000..8b223d9e5a
--- /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 0000000000..e8519c83c3
--- /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 0000000000..951d8c69e3
--- /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 0000000000..5982143bcb
--- /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 0000000000..bb50c6534f
--- /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 0000000000..2781a35757
--- /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 0000000000..271329209a
--- /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 0000000000..a72dc893ed
--- /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 0000000000..30e843c028
--- /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 0000000000..c31f15ec01
--- /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 0000000000..555da9e098
--- /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 0000000000..43e5a13b49
--- /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 0000000000..aeb373e18e
--- /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 0000000000..0507f51d61
--- /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 0000000000..fb1b227ad7
--- /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 0000000000..3d2584fca8
--- /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 0000000000..e5d5336016
--- /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 0000000000..69b88032d5
--- /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 0000000000..f76d116a76
--- /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 0000000000..0193d95783
--- /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 0000000000..bfb6e8e9ed
--- /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 0000000000..497acabc23
--- /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 0000000000..2d1e7e00e4
--- /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 0000000000..fdf85a9b45
--- /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 0000000000..3282cbf467
--- /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 0000000000..c4921e5334
--- /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 0000000000..050118d3c4
--- /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 0000000000..3e46df2f24
--- /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 0000000000..c10bfb3656
--- /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 0000000000..83c488fe42
--- /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 0000000000..62aeb44738
--- /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 0000000000..639c9b558e
--- /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 0000000000..82602000aa
--- /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 0000000000..e21ea40d51
--- /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 0000000000..4819703197
--- /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 0000000000..7215d526e9
--- /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 0000000000..f3caaafb70
--- /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 0000000000..81e18faf47
--- /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 0000000000..b9292ac1a8
--- /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 0000000000..9ef74baa0c
--- /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 0000000000..99583cdf47
--- /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 0000000000..a94b61d319
--- /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 0000000000..3061b7d2fa
--- /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 0000000000..06893155bd
--- /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 0000000000..e46cee3a34
--- /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 0000000000..a2fbf720f9
--- /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 0000000000..fe79d242e8
--- /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 0000000000..da86450726
--- /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 0000000000..20b4319252
--- /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 0000000000..027a9de438
--- /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 0000000000..709ae0ce84
--- /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 0000000000..3741ffa110
--- /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 0000000000..32074b44cc
--- /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 0000000000..cc34640988
--- /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 0000000000..80b0f49989
--- /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 0000000000..ba197e986c
--- /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 0000000000..db5f876b53
--- /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 0000000000..c876def487
--- /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 0000000000..f1ebee276c
--- /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 0000000000..934b6b6cc1
--- /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 0000000000..99fdbfef5c
--- /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 0000000000..1976386c85
--- /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 0000000000..f3477648de
--- /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 0000000000..deba2d2697
--- /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 0000000000..961cc07841
--- /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 0000000000..476f8150a7
--- /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 0000000000..51e8ce5f58
--- /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 0000000000..ad1f7fce1f
--- /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 0000000000..d8abdbdbf6
--- /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 0000000000..416441991d
--- /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 0000000000..5c211c4368
--- /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 0000000000..f633fec226
--- /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 0000000000..848190b02e
--- /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 0000000000..3ce2702ec6
--- /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 0000000000..75cccabc9c
--- /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 0000000000..eb53233e33
--- /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 0000000000..cac18debbb
--- /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 0000000000..ce7c73997b
--- /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 0000000000..92a1d72521
--- /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 0000000000..95598f0a46
--- /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 0000000000..e7e8bf045a
--- /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 0000000000..8cda48eef3
--- /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 0000000000..49ee58b93e
--- /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 0000000000..9e8e3fd537
--- /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 0000000000..fb0a073f0b
--- /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 0000000000..b2916522a5
--- /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 0000000000..881b24fff5
--- /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 0000000000..d4cab49afe
--- /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 0000000000..ce89e5b054
--- /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 0000000000..af32d851b0
--- /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 0000000000..5cc06484a2
--- /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 0000000000..0b24a7d4f4
--- /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 0000000000..448e2c6f6d
--- /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 0000000000..a4f67ce4a2
--- /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 0000000000..60adc74379
--- /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 0000000000..946379df26
--- /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 0000000000..b012626ba9
--- /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 0000000000..d18eb7f257
--- /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 0000000000..1124043064
--- /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 0000000000..c0c9a0f6e5
--- /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 0000000000..c07fe1e9f1
--- /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 0000000000..482f2f481f
--- /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 0000000000..6474aeda58
--- /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 0000000000..f506c41c89
--- /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 0000000000..c1cb306149
--- /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 0000000000..9496b26bac
--- /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 0000000000..60c938db0a
--- /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 0000000000..1ccee896e7
--- /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 0000000000..a4337b7766
--- /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 0000000000..8a1e5d4908
--- /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 0000000000..6110d7f3db
--- /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 0000000000..dc8ec34a7c
--- /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 0000000000..7d049626c3
--- /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 0000000000..f92527f3f5
--- /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 0000000000..5d9fda8a75
--- /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 0000000000..eed9d80f88
--- /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 0000000000..40c40a8eb3
--- /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 0000000000..f4cfac52af
--- /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 0000000000..185fc40ac7
--- /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 0000000000..b6cebb2b9c
--- /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 0000000000..821b40cb19
--- /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 0000000000..2a5960b661
--- /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 0000000000..2bdc2a912f
--- /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 0000000000..6af7d138be
--- /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 0000000000..772df8dfd9
--- /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 0000000000..aecf353b29
--- /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 0000000000..dfdfeaf7d1
--- /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 0000000000..3261bce2f2
--- /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 0000000000..79afefa5c0
--- /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 0000000000..f407fb08e6
--- /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 0000000000..dd540ccc1b
--- /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 0000000000..4f5fe20916
--- /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 0000000000..85820f8ffc
--- /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 0000000000..f8e9e27d1c
--- /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 0000000000..a30e99603c
--- /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 0000000000..0313a730d5
--- /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 0000000000..a97c164925
--- /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 0000000000..2d3c6e23f0
--- /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 0000000000..8327f12509
--- /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 0000000000..07c1aa4e8d
--- /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 0000000000..4b8e9d71cb
--- /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 0000000000..902a003b26
--- /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 0000000000..1723ee6fd5
--- /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 0000000000..da9188dad3
--- /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 0000000000..25ae2af17a
--- /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 0000000000..e9feb391b1
--- /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 0000000000..12246922d4
--- /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 0000000000..6c5d92a7af
--- /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 0000000000..87e3c0318c
--- /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 0000000000..74c0422ef4
--- /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 0000000000..fd7573fa95
--- /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 0000000000..12067fef65
--- /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 0000000000..633e591186
--- /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 0000000000..286133ea54
--- /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 0000000000..1d01466284
--- /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 0000000000..fae14d9f7a
--- /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 0000000000..7d15dd87bf
--- /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 0000000000..ae6b147fd2
--- /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 0000000000..f4c7c0f2a9
--- /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 0000000000..11b045af4f
--- /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 0000000000..e73061667b
--- /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 0000000000..549d51132a
--- /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 0000000000..c0fe470806
--- /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 0000000000..b25c2c979a
--- /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 0000000000..3b72364182
--- /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 0000000000..279d6904f0
--- /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 0000000000..f04dade51b
--- /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 0000000000..c752f057df
--- /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 0000000000..27141a32f2
--- /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 0000000000..e79ecfee22
--- /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 0000000000..5016602123
--- /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 0000000000..38bd2ec27a
--- /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 0000000000..f7b23ed16f
--- /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 0000000000..237d311ae8
--- /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 0000000000..f10ed47b80
--- /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 0000000000..f52e71bfee
--- /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 0000000000..e66c96e05d
--- /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 0000000000..dfde3f33e3
--- /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 0000000000..3f61be9788
--- /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 0000000000..fae7f3d0ff
--- /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 0000000000..932e9bfe2c
--- /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 0000000000..c02000c72a
--- /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 0000000000..5a84221b57
--- /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 0000000000..9b0b5201d7
--- /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 0000000000..1bdac15766
--- /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 0000000000..9d3b957efb
--- /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 0000000000..1e2684130f
--- /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 0000000000..e434fe0b97
--- /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 0000000000..c1d116126a
--- /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 0000000000..bea03e466c
--- /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 0000000000..5d45ad2493
--- /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 0000000000..4f307f8f90
--- /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 0000000000..6e9444ac8f
--- /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 0000000000..55ba603642
--- /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 0000000000..9c8c3b42ba
--- /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 0000000000..7ce1041840
--- /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 0000000000..aed0fc91de
--- /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 0000000000..6bd780684d
--- /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 0000000000..2365299415
--- /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 0000000000..c34a3339d5
--- /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 0000000000..d63333dd0a
--- /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 0000000000..e7fbeaeaf3
--- /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 0000000000..7a2310a6fd
--- /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 0000000000..0ae4481e3d
--- /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 0000000000..5e3659d647
--- /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 0000000000..6af2ceccf9
--- /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 0000000000..c4d901322e
--- /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 0000000000..36ad77f678
--- /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 0000000000..f5b0a1e122
--- /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 0000000000..b7c77fc19f
--- /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 0000000000..27a2c0e285
--- /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 0000000000..9f4121f4cb
--- /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 0000000000..d02ad6322b
--- /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 0000000000..557ee42155
--- /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 0000000000..9a14b65712
--- /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 0000000000..6790133467
--- /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 0000000000..f091e3b166
--- /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 0000000000..17e521b215
--- /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 0000000000..2e8d5b0a1c
--- /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 0000000000..b8b667bf4e
--- /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 0000000000..34663937a5
--- /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 0000000000..df327d0758
--- /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 0000000000..a02a79088b
--- /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 0000000000..6bf0669b45
--- /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 0000000000..36781ef16e
--- /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 0000000000..cd37b360a4
--- /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 0000000000..0d9444b0f5
--- /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 0000000000..d1cf4e6cba
--- /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 0000000000..f9bfcdef7f
--- /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 0000000000..23709b2bb0
--- /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 0000000000..bc0b7a26ce
--- /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 0000000000..5a4676234c
--- /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 0000000000..8236a388a5
--- /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 0000000000..cccfe6d4e6
--- /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 0000000000..d0b6b6f6bf
--- /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 0000000000..492fa23dbe
--- /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 0000000000..b912f37116
--- /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 0000000000..280011b3e0
--- /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 0000000000..6ae7f9fc38
--- /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 0000000000..f216475db6
--- /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 0000000000..3ca05035f5
--- /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 0000000000..477282408d
--- /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 0000000000..b4a3f4d91f
--- /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 0000000000..806e756a8e
--- /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 0000000000..4821912240
--- /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 0000000000..620de44a92
--- /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 0000000000..c36a59d010
--- /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 0000000000..4af0a65246
--- /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 0000000000..7c6e3e033b
--- /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 0000000000..4344d0ae8e
--- /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 0000000000..5e5cd285f4
--- /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 0000000000..8675ad7f8c
--- /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 0000000000..932bd2b6e4
--- /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 0000000000..101f2b19eb
--- /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 0000000000..33f73cf191
--- /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 0000000000..43a7acab58
--- /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 0000000000..591f46a43e
--- /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 0000000000..7b17028cdf
--- /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 0000000000..279da7992b
--- /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 0000000000..e43e3ce7a9
--- /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 0000000000..03d642a018
--- /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 0000000000..ba983198e7
--- /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 0000000000..a317e99346
--- /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 0000000000..e1336c82ba
--- /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 0000000000..3bca310e82
--- /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 0000000000..9ad48986f5
--- /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 0000000000..dbe1d54dd0
--- /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 0000000000..4764c44a63
--- /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 0000000000..c072fc6f67
--- /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 0000000000..70106df027
--- /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 0000000000..5e5c44f89d
--- /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 0000000000..7a70d0015d
--- /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 0000000000..c4bb38f386
--- /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 0000000000..b31c7e9790
--- /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 0000000000..8a476b9f38
--- /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 0000000000..cf9574d7f8
--- /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 0000000000..4234b65aa6
--- /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 0000000000..6c7f3ad66f
--- /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 0000000000..14eb312608
--- /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 0000000000..0344c1a552
--- /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 0000000000..4838b99f5c
--- /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 0000000000..b65cae78a9
--- /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 0000000000..5fe43dedd2
--- /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 0000000000..a9fd28edba
--- /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 0000000000..e7eed159ea
--- /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 0000000000..f1355da4c2
--- /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 0000000000..7fad98704c
--- /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 0000000000..8211307418
--- /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 0000000000..359aff75b9
--- /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 0000000000..b728802914
--- /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 0000000000..c4c51fba0a
--- /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 0000000000..b2755961c8
--- /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 0000000000..58e2500a02
--- /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 0000000000..304ee6522f
--- /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 0000000000..10f1465f3a
--- /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 0000000000..9dd1fbf13f
--- /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 0000000000..70d7741f64
--- /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 0000000000..01af0455b8
--- /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 0000000000..958a4971fa
--- /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 0000000000..3f9478c5de
--- /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 0000000000..050088ccb7
--- /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 0000000000..5ae402e523
--- /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 0000000000..3c7d2d8255
--- /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 0000000000..0da1303835
--- /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 0000000000..2da50a5360
--- /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 0000000000..7952bd34b2
--- /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 0000000000..96138c630a
--- /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 0000000000..826ced168e
--- /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 0000000000..463a8d88d6
--- /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 0000000000..aa80ef1d7e
--- /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 0000000000..6bd1a0bb82
--- /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 0000000000..3aab17079d
--- /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 0000000000..7972e005d0
--- /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 0000000000..a32d2db5cc
--- /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 0000000000..e2c16a9cf2
--- /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 0000000000..896fe0fcb3
--- /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 0000000000..a83f1574eb
--- /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 0000000000..0a7b5e20a9
--- /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 0000000000..134efbb515
--- /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 0000000000..062342fa4c
--- /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 0000000000..7847e1384e
--- /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 0000000000..8a91421e65
--- /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 0000000000..d66ea98fed
--- /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 0000000000..f3c77abb38
--- /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 0000000000..5607ab87aa
--- /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 0000000000..bb7f71248f
--- /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 0000000000..58166bb572
--- /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 0000000000..c61e139ac3
--- /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 0000000000..28d24befe0
--- /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 0000000000..aedd176bec
--- /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 0000000000..745cca7ebd
--- /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 0000000000..bb970d21a4
--- /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 0000000000..916b15e020
--- /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
index 6f6c10ce11..6f6c10ce11 100644
--- a/src/templating/.gitignore
+++ b/src/Templating/.gitignore
diff --git a/src/Templating/Directory.Build.props b/src/Templating/Directory.Build.props
new file mode 100644
index 0000000000..9887edd5d5
--- /dev/null
+++ b/src/Templating/Directory.Build.props
@@ -0,0 +1,17 @@
+<Project>
+ <Import
+ Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), AspNetCoreSettings.props))\AspNetCoreSettings.props"
+ Condition=" '$(CI)' != 'true' AND '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), AspNetCoreSettings.props))' != '' " />
+
+ <Import Project="version.props" />
+ <Import Project="build\dependencies.props" />
+ <Import Project="build\sources.props" />
+
+ <PropertyGroup>
+ <Product>Microsoft ASP.NET Core</Product>
+ <RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
+ <RepositoryUrl>https://github.com/aspnet/Templating</RepositoryUrl>
+ <RepositoryType>git</RepositoryType>
+ <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+ </PropertyGroup>
+</Project>
diff --git a/src/templating/Directory.Build.targets b/src/Templating/Directory.Build.targets
index eb03b2565f..eb03b2565f 100644
--- a/src/templating/Directory.Build.targets
+++ b/src/Templating/Directory.Build.targets
diff --git a/src/templating/NuGetPackageVerifier.json b/src/Templating/NuGetPackageVerifier.json
index c3c62e30d3..c3c62e30d3 100644
--- a/src/templating/NuGetPackageVerifier.json
+++ b/src/Templating/NuGetPackageVerifier.json
diff --git a/src/templating/README.md b/src/Templating/README.md
index 3d1087be91..3d1087be91 100644
--- a/src/templating/README.md
+++ b/src/Templating/README.md
diff --git a/src/templating/Templating.sln b/src/Templating/Templating.sln
index e98539fcdb..e98539fcdb 100644
--- a/src/templating/Templating.sln
+++ b/src/Templating/Templating.sln
diff --git a/src/templating/build/dependencies.props b/src/Templating/build/dependencies.props
index 3110c18d64..3110c18d64 100644
--- a/src/templating/build/dependencies.props
+++ b/src/Templating/build/dependencies.props
diff --git a/src/templating/build/repo.props b/src/Templating/build/repo.props
index 8bc3037a4c..8bc3037a4c 100644
--- a/src/templating/build/repo.props
+++ b/src/Templating/build/repo.props
diff --git a/src/templating/build/sources.props b/src/Templating/build/sources.props
index 9215df9751..9215df9751 100644
--- a/src/templating/build/sources.props
+++ b/src/Templating/build/sources.props
diff --git a/src/templating/src/Directory.Build.props b/src/Templating/src/Directory.Build.props
index 03608a0aa8..03608a0aa8 100644
--- a/src/templating/src/Directory.Build.props
+++ b/src/Templating/src/Directory.Build.props
diff --git a/src/templating/src/Directory.Build.targets b/src/Templating/src/Directory.Build.targets
index 0b5bb534e7..0b5bb534e7 100644
--- a/src/templating/src/Directory.Build.targets
+++ b/src/Templating/src/Directory.Build.targets
diff --git a/src/templating/src/GenerateContent.targets b/src/Templating/src/GenerateContent.targets
index 96251c178b..96251c178b 100644
--- a/src/templating/src/GenerateContent.targets
+++ b/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
index fa34138de5..fa34138de5 100644
--- 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
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
index 94f4ee1014..94f4ee1014 100644
--- 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
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
index 9d379e9c9f..9d379e9c9f 100644
--- 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
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
index 46800d16ad..46800d16ad 100644
--- 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
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
index 94f4ee1014..94f4ee1014 100644
--- 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
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
index f720f59f3d..f720f59f3d 100644
--- 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
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
index 46800d16ad..46800d16ad 100644
--- 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
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
index 94f4ee1014..94f4ee1014 100644
--- 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
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
index 38be31e1d3..38be31e1d3 100644
--- 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
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
index c031161736..c031161736 100644
--- 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
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
index 6a6bf62bb8..6a6bf62bb8 100644
--- 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
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
index 94f4ee1014..94f4ee1014 100644
--- 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
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
index e58b009ae4..e58b009ae4 100644
--- 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
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
index 3fa062c015..3fa062c015 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/RazorPage/Index.cshtml
+++ b/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
index 45faf36c3e..45faf36c3e 100644
--- 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
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
index 94f4ee1014..94f4ee1014 100644
--- 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
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
index 1b42ce63b7..1b42ce63b7 100644
--- 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
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
index 041eb30479..041eb30479 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewImports/_ViewImports.cshtml
+++ b/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
index 94f4ee1014..94f4ee1014 100644
--- 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
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
index 08d4427017..08d4427017 100644
--- 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
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
index 1af6e49466..1af6e49466 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ItemTemplates/content/ViewStart/_ViewStart.cshtml
+++ b/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
index dc70ec650b..dc70ec650b 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/.gitignore
+++ b/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
index 769cc51bb8..769cc51bb8 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/EmptyWeb-CSharp.csproj.in
+++ b/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
index 5e029bbfd5..5e029bbfd5 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/EmptyWeb-FSharp.fsproj.in
+++ b/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
index fb30191ade..fb30191ade 100644
--- 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
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
index fb5155ba60..fb5155ba60 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/RazorClassLibrary-CSharp.csproj.in
+++ b/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
index dc190443c5..dc190443c5 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/RazorPagesWeb-CSharp.csproj.in
+++ b/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
index a053ae2edc..a053ae2edc 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/StarterWeb-CSharp.csproj.in
+++ b/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
index 43aa9c21f6..43aa9c21f6 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/StarterWeb-FSharp.fsproj.in
+++ b/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
index 45d3752a9d..45d3752a9d 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/WebApi-CSharp.csproj.in
+++ b/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
index 9a435ae260..9a435ae260 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/WebApi-FSharp.fsproj.in
+++ b/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
index 7916bd8054..7916bd8054 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/Directory.Build.props
+++ b/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
index 0f803ab0e0..0f803ab0e0 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/Directory.Build.targets
+++ b/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
index 23f780ef98..23f780ef98 100644
--- 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
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
index 0b474de835..0b474de835 100644
--- 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
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
index a4dca6b104..a4dca6b104 100644
--- 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
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
index ea4b7e2492..ea4b7e2492 100644
--- 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
Binary files differ
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
index 4fda9f8d66..4fda9f8d66 100644
--- 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
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
index c327c4f218..c327c4f218 100644
--- 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
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
index d039a38a68..d039a38a68 100644
--- 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
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
index a6ad828311..a6ad828311 100644
--- 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
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/-.-
index e69de29bb2..e69de29bb2 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/EmptyWeb-CSharp/wwwroot/-.-
+++ b/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
index 9365baf180..9365baf180 100644
--- 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
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
index 332ead6554..332ead6554 100644
--- 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
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
index 1aa6393b7e..1aa6393b7e 100644
--- 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
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
index ea4b7e2492..ea4b7e2492 100644
--- 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
Binary files differ
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
index 5173fe80c3..5173fe80c3 100644
--- 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
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
index ed04d08212..ed04d08212 100644
--- 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
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
index 2709a8aa3c..2709a8aa3c 100644
--- 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
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
index a6ad828311..a6ad828311 100644
--- 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
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
index a0e1462eeb..a0e1462eeb 100644
--- 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
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
index 93b250c60c..93b250c60c 100644
--- 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
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
index 0e58355fdc..0e58355fdc 100644
--- 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
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
index 9d01cf3fd2..9d01cf3fd2 100644
--- 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
Binary files differ
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
index 2927c441cd..2927c441cd 100644
--- 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
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
index 07a0edacfc..07a0edacfc 100644
--- 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
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
index 9300d123ce..9300d123ce 100644
--- 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
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
index 154d9094f2..154d9094f2 100644
--- 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
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
index fd13c870d9..fd13c870d9 100644
--- 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
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
index 572a095fa0..572a095fa0 100644
--- 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
Binary files differ
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
index 7bd9b6bb87..7bd9b6bb87 100644
--- 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
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
index a33a15e3e9..a33a15e3e9 100644
--- 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
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
index 3392f2ce19..3392f2ce19 100644
--- 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
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
index ac844c831f..ac844c831f 100644
--- 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
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
index 15daad4135..15daad4135 100644
--- 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
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
index d09bb7025c..d09bb7025c 100644
--- 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
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
index ef09a15377..ef09a15377 100644
--- 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
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
index 261af85933..261af85933 100644
--- 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
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
index 3c090d15f0..3c090d15f0 100644
--- 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
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
index 74a71cafa8..74a71cafa8 100644
--- 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
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
index b683c8216c..b683c8216c 100644
--- 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
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
index 303ac65a8b..303ac65a8b 100644
--- 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
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
index b1f3143a42..b1f3143a42 100644
--- 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
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
index 9cb1d63663..9cb1d63663 100644
--- 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
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
index ff7fc32dfc..ff7fc32dfc 100644
--- 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
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
index 1c86e8c152..1c86e8c152 100644
--- 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
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
index f3787bacd6..f3787bacd6 100644
--- 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
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
index ff3db9b067..ff3db9b067 100644
--- 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
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
index 0360a039a8..0360a039a8 100644
--- 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
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
index d6efc1ba53..d6efc1ba53 100644
--- 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
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
index 3cd78ca58a..3cd78ca58a 100644
--- 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
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
index 124a84495c..124a84495c 100644
--- 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
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
index ef848fe8fc..ef848fe8fc 100644
--- 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
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
index 7fdcd89164..7fdcd89164 100644
--- 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
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
index a5f10045db..a5f10045db 100644
--- 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
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
index 4fda9f8d66..4fda9f8d66 100644
--- 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
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
index 943010d3ef..943010d3ef 100644
--- 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
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
index 6423b8010f..6423b8010f 100644
--- 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
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
index a6ad828311..a6ad828311 100644
--- 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
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
index ec163057f1..ec163057f1 100644
--- 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
Binary files differ
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
index e203e9407e..e203e9407e 100644
--- 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
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
index 0f893b1b48..0f893b1b48 100644
--- 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
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
index dfda2821c7..dfda2821c7 100644
--- 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
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
index 5e93e30ae3..5e93e30ae3 100644
--- 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
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
index a3a799985c..a3a799985c 100644
--- 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
Binary files differ
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
index 1ab32b60b8..1ab32b60b8 100644
--- 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
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
index 9679c604d0..9679c604d0 100644
--- 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
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
index 38b3d7cd1f..38b3d7cd1f 100644
--- 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
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
index 3c76e6dc45..3c76e6dc45 100644
--- 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
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
index e69de29bb2..e69de29bb2 100644
--- 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
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
index 1e99b62994..1e99b62994 100644
--- 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
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
index 7a300022c3..7a300022c3 100644
--- 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
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
index 31d8882661..31d8882661 100644
--- 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
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
index d876f60fb4..d876f60fb4 100644
--- 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
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
index 5e39401957..5e39401957 100644
--- 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
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
index 94813e9006..94813e9006 100644
--- 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
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
index 6167622cec..6167622cec 100644
--- 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
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
index f010c82d11..f010c82d11 100644
--- 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
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
index ed3905e0e0..ed3905e0e0 100644
--- 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
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
index 6c7fa40b98..6c7fa40b98 100644
--- 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
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
index b93a4953ff..b93a4953ff 100644
--- 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
Binary files differ
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
index 94fb5490a2..94fb5490a2 100644
--- 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
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
index 1413fc609a..1413fc609a 100644
--- 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
Binary files differ
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
index 9e612858f8..9e612858f8 100644
--- 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
Binary files differ
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
index 64539b54c3..64539b54c3 100644
--- 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
Binary files differ
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
index 8a2e99a535..8a2e99a535 100644
--- 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
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
index 9bcd2fccae..9bcd2fccae 100644
--- 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
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
index bf6aa80602..bf6aa80602 100644
--- 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
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
index 4f4ad9756f..4f4ad9756f 100644
--- 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
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
index 0bdc1962b6..0bdc1962b6 100644
--- 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
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
index 6af3efad07..6af3efad07 100644
--- 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
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
index 5f6e039b62..5f6e039b62 100644
--- 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
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
index 79824166a0..79824166a0 100644
--- 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
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
index dc377cc031..dc377cc031 100644
--- 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
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
index e129bc0f74..e129bc0f74 100644
--- 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
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
index 6767f24f6b..6767f24f6b 100644
--- 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
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
index 12674b08b2..12674b08b2 100644
--- 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
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
index 20402da5d3..20402da5d3 100644
--- 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
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
index 959ddb7137..959ddb7137 100644
--- 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
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
index e4e5e00ef0..e4e5e00ef0 100644
--- 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
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
index 9b5206bcc6..9b5206bcc6 100644
--- 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
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
index 4d9b3a2587..4d9b3a2587 100644
--- 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
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
index 294ee966c8..294ee966c8 100644
--- 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
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
index 1b8cfa12df..1b8cfa12df 100644
--- 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
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
index ae39c7f26e..ae39c7f26e 100644
--- 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
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
index e41095f10b..e41095f10b 100644
--- 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
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
index 572a095fa0..572a095fa0 100644
--- 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
Binary files differ
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
index c4284f6c20..c4284f6c20 100644
--- 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
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
index 87e88e9bd2..87e88e9bd2 100644
--- 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
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
index a33a15e3e9..a33a15e3e9 100644
--- 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
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
index 3392f2ce19..3392f2ce19 100644
--- 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
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
index ac844c831f..ac844c831f 100644
--- 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
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
index 15daad4135..15daad4135 100644
--- 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
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
index d09bb7025c..d09bb7025c 100644
--- 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
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
index ef09a15377..ef09a15377 100644
--- 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
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
index 261af85933..261af85933 100644
--- 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
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
index 592c52772e..592c52772e 100644
--- 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
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
index 4fda9f8d66..4fda9f8d66 100644
--- 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
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
index 943010d3ef..943010d3ef 100644
--- 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
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
index 4cdb0ff356..4cdb0ff356 100644
--- 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
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
index 3674e37a86..3674e37a86 100644
--- 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
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
index a11a1867cf..a11a1867cf 100644
--- 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
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
index f42d2a08bd..f42d2a08bd 100644
--- 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
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
index 7bd38619c6..7bd38619c6 100644
--- 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
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
index ec2ea6bd03..ec2ea6bd03 100644
--- 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
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
index bbfbb09acb..bbfbb09acb 100644
--- 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
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
index 7e68378c09..7e68378c09 100644
--- 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
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
index 29636d8977..29636d8977 100644
--- 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
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
index 37e437dc07..37e437dc07 100644
--- 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
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
index 2a9241f596..2a9241f596 100644
--- 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
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
index 9ec36ac038..9ec36ac038 100644
--- 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
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
index a5f10045db..a5f10045db 100644
--- 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
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
index a6ad828311..a6ad828311 100644
--- 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
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
index ec163057f1..ec163057f1 100644
--- 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
Binary files differ
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
index e203e9407e..e203e9407e 100644
--- 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
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
index 0f893b1b48..0f893b1b48 100644
--- 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
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
index e89c7811cd..e89c7811cd 100644
--- 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
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
index 5e93e30ae3..5e93e30ae3 100644
--- 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
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
index a3a799985c..a3a799985c 100644
--- 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
Binary files differ
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
index 1ab32b60b8..1ab32b60b8 100644
--- 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
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
index 9679c604d0..9679c604d0 100644
--- 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
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
index 38b3d7cd1f..38b3d7cd1f 100644
--- 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
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
index ac49c18641..ac49c18641 100644
--- 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
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
index e69de29bb2..e69de29bb2 100644
--- 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
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
index 1e99b62994..1e99b62994 100644
--- 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
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
index 7a300022c3..7a300022c3 100644
--- 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
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
index 31d8882661..31d8882661 100644
--- 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
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
index d876f60fb4..d876f60fb4 100644
--- 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
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
index 5e39401957..5e39401957 100644
--- 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
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
index 94813e9006..94813e9006 100644
--- 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
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
index 6167622cec..6167622cec 100644
--- 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
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
index f010c82d11..f010c82d11 100644
--- 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
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
index ed3905e0e0..ed3905e0e0 100644
--- 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
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
index 6c7fa40b98..6c7fa40b98 100644
--- 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
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
index b93a4953ff..b93a4953ff 100644
--- 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
Binary files differ
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
index 94fb5490a2..94fb5490a2 100644
--- 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
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
index 1413fc609a..1413fc609a 100644
--- 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
Binary files differ
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
index 9e612858f8..9e612858f8 100644
--- 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
Binary files differ
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
index 64539b54c3..64539b54c3 100644
--- 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
Binary files differ
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
index 8a2e99a535..8a2e99a535 100644
--- 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
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
index 9bcd2fccae..9bcd2fccae 100644
--- 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
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
index bf6aa80602..bf6aa80602 100644
--- 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
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
index 4f4ad9756f..4f4ad9756f 100644
--- 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
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
index 0bdc1962b6..0bdc1962b6 100644
--- 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
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
index 6af3efad07..6af3efad07 100644
--- 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
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
index 5f6e039b62..5f6e039b62 100644
--- 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
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
index 79824166a0..79824166a0 100644
--- 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
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
index dc377cc031..dc377cc031 100644
--- 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
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
index e129bc0f74..e129bc0f74 100644
--- 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
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
index 6767f24f6b..6767f24f6b 100644
--- 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
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
index 12674b08b2..12674b08b2 100644
--- 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
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
index 20402da5d3..20402da5d3 100644
--- 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
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
index 959ddb7137..959ddb7137 100644
--- 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
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
index e4e5e00ef0..e4e5e00ef0 100644
--- 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
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
index 9b5206bcc6..9b5206bcc6 100644
--- 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
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
index 4d9b3a2587..4d9b3a2587 100644
--- 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
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
index 294ee966c8..294ee966c8 100644
--- 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
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
index 6406626abf..6406626abf 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/StarterWeb-FSharp/.bowerrc
+++ b/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
index 3ea30fe564..3ea30fe564 100644
--- 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
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
index 44f417b380..44f417b380 100644
--- 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
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
index 13271b6136..13271b6136 100644
--- 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
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
index b028e7d4e4..b028e7d4e4 100644
--- 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
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
index 6734a26d7b..6734a26d7b 100644
--- 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
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
index 81a2247b13..81a2247b13 100644
--- 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
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
index e54cde90e3..e54cde90e3 100644
--- 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
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
index 50476d1fbd..50476d1fbd 100644
--- 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
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
index 15c12c6d12..15c12c6d12 100644
--- 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
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
index f42d2a08bd..f42d2a08bd 100644
--- 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
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
index ec2ea6bd03..ec2ea6bd03 100644
--- 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
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
index 76cfabcb6c..76cfabcb6c 100644
--- 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
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
index ef848fe8fc..ef848fe8fc 100644
--- 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
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
index e3f167d1f5..e3f167d1f5 100644
--- 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
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
index a5f10045db..a5f10045db 100644
--- 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
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
index a6ad828311..a6ad828311 100644
--- 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
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
index e203e9407e..e203e9407e 100644
--- 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
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
index def9159a7d..def9159a7d 100644
--- 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
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
index 9e72fca35e..9e72fca35e 100644
--- 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
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
index 3beb45f52f..3beb45f52f 100644
--- 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
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
index a3a799985c..a3a799985c 100644
--- 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
Binary files differ
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
index 1ab32b60b8..1ab32b60b8 100644
--- 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
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
index 9679c604d0..9679c604d0 100644
--- 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
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
index 38b3d7cd1f..38b3d7cd1f 100644
--- 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
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
index 3c76e6dc45..3c76e6dc45 100644
--- 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
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
index e69de29bb2..e69de29bb2 100644
--- 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
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
index 1e99b62994..1e99b62994 100644
--- 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
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
index 7a300022c3..7a300022c3 100644
--- 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
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
index 31d8882661..31d8882661 100644
--- 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
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
index d876f60fb4..d876f60fb4 100644
--- 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
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
index 5e39401957..5e39401957 100644
--- 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
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
index 94813e9006..94813e9006 100644
--- 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
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
index 6167622cec..6167622cec 100644
--- 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
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
index f010c82d11..f010c82d11 100644
--- 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
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
index ed3905e0e0..ed3905e0e0 100644
--- 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
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
index 6c7fa40b98..6c7fa40b98 100644
--- 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
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
index b93a4953ff..b93a4953ff 100644
--- 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
Binary files differ
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
index 94fb5490a2..94fb5490a2 100644
--- 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
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
index 1413fc609a..1413fc609a 100644
--- 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
Binary files differ
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
index 9e612858f8..9e612858f8 100644
--- 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
Binary files differ
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
index 64539b54c3..64539b54c3 100644
--- 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
Binary files differ
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
index 8a2e99a535..8a2e99a535 100644
--- 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
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
index 9bcd2fccae..9bcd2fccae 100644
--- 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
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
index bf6aa80602..bf6aa80602 100644
--- 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
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
index 4f4ad9756f..4f4ad9756f 100644
--- 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
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
index 0bdc1962b6..0bdc1962b6 100644
--- 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
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
index 6af3efad07..6af3efad07 100644
--- 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
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
index 5f6e039b62..5f6e039b62 100644
--- 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
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
index 79824166a0..79824166a0 100644
--- 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
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
index dc377cc031..dc377cc031 100644
--- 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
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
index e129bc0f74..e129bc0f74 100644
--- 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
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
index 6767f24f6b..6767f24f6b 100644
--- 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
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
index 12674b08b2..12674b08b2 100644
--- 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
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
index 20402da5d3..20402da5d3 100644
--- 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
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
index 959ddb7137..959ddb7137 100644
--- 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
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
index e4e5e00ef0..e4e5e00ef0 100644
--- 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
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
index 9b5206bcc6..9b5206bcc6 100644
--- 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
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
index 4d9b3a2587..4d9b3a2587 100644
--- 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
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
index 294ee966c8..294ee966c8 100644
--- 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
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
index 5922b1d611..5922b1d611 100644
--- 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
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
index 2374e93237..2374e93237 100644
--- 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
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
index 9e71ead6d7..9e71ead6d7 100644
--- 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
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
index 61ba1afdce..61ba1afdce 100644
--- 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
Binary files differ
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
index 55a45bf67f..55a45bf67f 100644
--- 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
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
index 4fda9f8d66..4fda9f8d66 100644
--- 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
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
index ce551d4480..ce551d4480 100644
--- 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
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
index 93e87090fd..93e87090fd 100644
--- 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
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
index a6ad828311..a6ad828311 100644
--- 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
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
index e203e9407e..e203e9407e 100644
--- 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
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
index 79300faae7..79300faae7 100644
--- 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
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/-.-
index e69de29bb2..e69de29bb2 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-CSharp/wwwroot/-.-
+++ b/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
index 3ea30fe564..3ea30fe564 100644
--- 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
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
index 901221dcd1..901221dcd1 100644
--- 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
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
index c372818036..c372818036 100644
--- 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
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
index 61ba1afdce..61ba1afdce 100644
--- 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
Binary files differ
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
index c77d5d4b79..c77d5d4b79 100644
--- 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
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
index c37ddcb601..c37ddcb601 100644
--- 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
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
index baa3ecdda1..baa3ecdda1 100644
--- 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
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
index cbc5f4e5b6..cbc5f4e5b6 100644
--- 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
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
index a6ad828311..a6ad828311 100644
--- 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
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
index e203e9407e..e203e9407e 100644
--- 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
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
index def9159a7d..def9159a7d 100644
--- 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
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/-.-
index e69de29bb2..e69de29bb2 100644
--- a/src/templating/src/Microsoft.DotNet.Web.ProjectTemplates/content/WebApi-FSharp/wwwroot/-.-
+++ b/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
index dc70ec650b..dc70ec650b 100644
--- a/src/templating/src/Microsoft.DotNet.Web.Spa.ProjectTemplates/.gitignore
+++ b/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
index 4919fe59bb..4919fe59bb 100644
--- 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
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
index 576f467305..576f467305 100644
--- 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
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
index 896d7a3bde..896d7a3bde 100644
--- 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
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
index 12995468dc..12995468dc 100644
--- 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
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
index 0d10972f36..0d10972f36 100644
--- 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
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
index 066179f4f4..066179f4f4 100644
--- 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
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
index 0d47965db7..0d47965db7 100644
--- 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
Binary files differ
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
index e7b2a59892..e7b2a59892 100644
--- 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
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
index 0a13a854c9..0a13a854c9 100644
--- 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
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
index da6fdbc1b3..da6fdbc1b3 100644
--- 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
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
index 6e87a003da..6e87a003da 100644
--- 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
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
index 4ae2c37916..4ae2c37916 100644
--- 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
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
index 0cf4e2c377..0cf4e2c377 100644
--- 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
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
index 5b3b4b27d7..5b3b4b27d7 100644
--- 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
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
index 24bc8b3cfc..24bc8b3cfc 100644
--- 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
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
index 1d9e5edf09..1d9e5edf09 100644
--- 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
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
index af139fada3..af139fada3 100644
--- 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
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
index f2a5447370..f2a5447370 100644
--- 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
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
index cdba0848c7..cdba0848c7 100644
--- 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
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
index 7ee3b5ee86..7ee3b5ee86 100644
--- 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
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
index 33bfcb7a92..33bfcb7a92 100644
--- 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
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
index e71d5ce2e9..e71d5ce2e9 100644
--- 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
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
index 7b0f672831..7b0f672831 100644
--- 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
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
index cecddedb45..cecddedb45 100644
--- 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
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
index 2521eda7af..2521eda7af 100644
--- 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
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
index 026a91a062..026a91a062 100644
--- 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
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
index 1f336aa976..1f336aa976 100644
--- 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
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
index d065b828f0..d065b828f0 100644
--- 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
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
index c120befb86..c120befb86 100644
--- 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
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
index f74c2e7845..f74c2e7845 100644
--- 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
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
index 2747b30230..2747b30230 100644
--- 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
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
index e15c612895..e15c612895 100644
--- 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
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
index e8a1d4080d..e8a1d4080d 100644
--- 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
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
index 327a3743f0..327a3743f0 100644
--- 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
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
index e69de29bb2..e69de29bb2 100644
--- 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
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
index 3612073bc3..3612073bc3 100644
--- 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
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
index b7f639aeca..b7f639aeca 100644
--- 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
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
index 1d75b6afd1..1d75b6afd1 100644
--- 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
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
index a2f708cba9..a2f708cba9 100644
--- 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
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
index af84770782..af84770782 100644
--- 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
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
index 90d4ee0072..90d4ee0072 100644
--- 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
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
index 16317897b1..16317897b1 100644
--- 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
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
index 39ba8dbacb..39ba8dbacb 100644
--- 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
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
index ac22a298ac..ac22a298ac 100644
--- 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
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
index ef5c7bd620..ef5c7bd620 100644
--- 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
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
index a6c016bf38..a6c016bf38 100644
--- 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
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
index 9963d6c395..9963d6c395 100644
--- 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
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
index 4078df55b6..4078df55b6 100644
--- 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
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
index b1f3143a42..b1f3143a42 100644
--- 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
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
index 9cb1d63663..9cb1d63663 100644
--- 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
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
index 83b0afbea3..83b0afbea3 100644
--- 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
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
index a7108005ed..a7108005ed 100644
--- 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
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
index 3d3de39c77..3d3de39c77 100644
--- 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
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
index 4cad62f327..4cad62f327 100644
--- 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
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
index a6ad828311..a6ad828311 100644
--- 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
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
index e203e9407e..e203e9407e 100644
--- 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
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
index def9159a7d..def9159a7d 100644
--- 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
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
index a3a799985c..a3a799985c 100644
--- 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
Binary files differ
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
index 7916bd8054..7916bd8054 100644
--- 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
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
index 0f803ab0e0..0f803ab0e0 100644
--- 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
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
index c72f0caa76..c72f0caa76 100644
--- 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
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
index 066179f4f4..066179f4f4 100644
--- 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
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
index dd6fe66c25..dd6fe66c25 100644
--- 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
Binary files differ
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
index df23986cfa..df23986cfa 100644
--- 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
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
index 32b54c3ba9..32b54c3ba9 100644
--- 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
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
index d30f40ef44..d30f40ef44 100644
--- 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
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
index b8c0b74df3..b8c0b74df3 100644
--- 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
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
index d5171611aa..d5171611aa 100644
--- 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
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
index 2515ded975..2515ded975 100644
--- 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
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
index a3a799985c..a3a799985c 100644
--- 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
Binary files differ
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
index 7607dcc74d..7607dcc74d 100644
--- 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
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
index e31eef554b..e31eef554b 100644
--- 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
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
index ef08afaa6d..ef08afaa6d 100644
--- 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
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
index b84af98d72..b84af98d72 100644
--- 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
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
index 3d6f3b1534..3d6f3b1534 100644
--- 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
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
index eaf9371778..eaf9371778 100644
--- 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
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
index 894c2bdc38..894c2bdc38 100644
--- 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
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
index 4057004c1d..4057004c1d 100644
--- 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
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
index e2b116d2bb..e2b116d2bb 100644
--- 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
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
index 7ab971d71a..7ab971d71a 100644
--- 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
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
index 0b8db1f092..0b8db1f092 100644
--- 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
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
index b7cb11357a..b7cb11357a 100644
--- 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
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
index 12542ba229..12542ba229 100644
--- 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
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
index 4078df55b6..4078df55b6 100644
--- 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
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
index b1f3143a42..b1f3143a42 100644
--- 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
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
index 9cb1d63663..9cb1d63663 100644
--- 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
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
index 83b0afbea3..83b0afbea3 100644
--- 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
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
index a7108005ed..a7108005ed 100644
--- 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
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
index 3d3de39c77..3d3de39c77 100644
--- 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
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
index 52123f94bf..52123f94bf 100644
--- 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
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
index a6ad828311..a6ad828311 100644
--- 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
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
index e203e9407e..e203e9407e 100644
--- 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
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
index def9159a7d..def9159a7d 100644
--- 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
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
index c72f0caa76..c72f0caa76 100644
--- 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
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
index 5fcb131918..5fcb131918 100644
--- 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
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
index e83eb39b86..e83eb39b86 100644
--- 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
Binary files differ
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
index 91b5774e42..91b5774e42 100644
--- 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
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
index 28c7d1a3a5..28c7d1a3a5 100644
--- 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
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
index d30f40ef44..d30f40ef44 100644
--- 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
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
index b8c0b74df3..b8c0b74df3 100644
--- 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
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
index f82ed84cd8..f82ed84cd8 100644
--- 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
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
index 8014d1c71e..8014d1c71e 100644
--- 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
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
index a3a799985c..a3a799985c 100644
--- 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
Binary files differ
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
index 7607dcc74d..7607dcc74d 100644
--- 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
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
index e31eef554b..e31eef554b 100644
--- 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
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
index de030457d0..de030457d0 100644
--- 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
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
index b84af98d72..b84af98d72 100644
--- 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
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
index dd17e90d53..dd17e90d53 100644
--- 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
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
index 88ae12f6d7..88ae12f6d7 100644
--- 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
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
index d2d6c8258b..d2d6c8258b 100644
--- 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
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
index 14a2abaeee..14a2abaeee 100644
--- 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
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
index e2b116d2bb..e2b116d2bb 100644
--- 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
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
index bf5e3f3411..bf5e3f3411 100644
--- 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
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
index 0b8db1f092..0b8db1f092 100644
--- 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
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
index b39f870084..b39f870084 100644
--- 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
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
index 12542ba229..12542ba229 100644
--- 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
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
index c400a7065c..c400a7065c 100644
--- 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
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
index dd42ddbac3..dd42ddbac3 100644
--- 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
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
index 6ddd81f27b..6ddd81f27b 100644
--- 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
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
index 4167f03915..4167f03915 100644
--- 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
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
index b1f3143a42..b1f3143a42 100644
--- 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
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
index 9cb1d63663..9cb1d63663 100644
--- 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
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
index 83b0afbea3..83b0afbea3 100644
--- 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
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
index a7108005ed..a7108005ed 100644
--- 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
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
index 3d3de39c77..3d3de39c77 100644
--- 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
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
index 52123f94bf..52123f94bf 100644
--- 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
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
index a6ad828311..a6ad828311 100644
--- 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
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
index e203e9407e..e203e9407e 100644
--- 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
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
index def9159a7d..def9159a7d 100644
--- 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
diff --git a/src/templating/src/SetPackageProperties.targets b/src/Templating/src/SetPackageProperties.targets
index 0d4c99dde3..0d4c99dde3 100644
--- a/src/templating/src/SetPackageProperties.targets
+++ b/src/Templating/src/SetPackageProperties.targets
diff --git a/src/templating/src/THIRD-PARTY-NOTICES b/src/Templating/src/THIRD-PARTY-NOTICES
index 7ec298c1e5..7ec298c1e5 100644
--- a/src/templating/src/THIRD-PARTY-NOTICES
+++ b/src/Templating/src/THIRD-PARTY-NOTICES
diff --git a/src/templating/src/templates.nuspec b/src/Templating/src/templates.nuspec
index da8ee99dee..da8ee99dee 100644
--- a/src/templating/src/templates.nuspec
+++ b/src/Templating/src/templates.nuspec
diff --git a/src/templating/test/Directory.Build.targets b/src/Templating/test/Directory.Build.targets
index 5baa2f8797..5baa2f8797 100644
--- a/src/templating/test/Directory.Build.targets
+++ b/src/Templating/test/Directory.Build.targets
diff --git a/src/templating/test/DotNetToolsInstaller/DotNetToolsInstaller.csproj b/src/Templating/test/DotNetToolsInstaller/DotNetToolsInstaller.csproj
index dcf5360950..dcf5360950 100644
--- a/src/templating/test/DotNetToolsInstaller/DotNetToolsInstaller.csproj
+++ b/src/Templating/test/DotNetToolsInstaller/DotNetToolsInstaller.csproj
diff --git a/src/templating/test/GenerateTestProps.targets b/src/Templating/test/GenerateTestProps.targets
index c4eaba5740..c4eaba5740 100644
--- a/src/templating/test/GenerateTestProps.targets
+++ b/src/Templating/test/GenerateTestProps.targets
diff --git a/src/templating/test/TemplateTests.props.in b/src/Templating/test/TemplateTests.props.in
index ed988d8f32..ed988d8f32 100644
--- a/src/templating/test/TemplateTests.props.in
+++ b/src/Templating/test/TemplateTests.props.in
diff --git a/src/templating/test/Templates.Test/.gitattributes b/src/Templating/test/Templates.Test/.gitattributes
index 300e504f3e..300e504f3e 100644
--- a/src/templating/test/Templates.Test/.gitattributes
+++ b/src/Templating/test/Templates.Test/.gitattributes
diff --git a/src/templating/test/Templates.Test/BaselineTest.cs b/src/Templating/test/Templates.Test/BaselineTest.cs
index d39ca267b0..d39ca267b0 100644
--- a/src/templating/test/Templates.Test/BaselineTest.cs
+++ b/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
index 1feefd3e7f..1feefd3e7f 100644
--- a/src/templating/test/Templates.Test/ByteOrderMarkTest.cs
+++ b/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
index 8351c97779..8351c97779 100644
--- a/src/templating/test/Templates.Test/CdnScriptTagTests.cs
+++ b/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
index 08873b6362..08873b6362 100644
--- a/src/templating/test/Templates.Test/EmptyWebTemplateTest.cs
+++ b/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
index 5387548a11..5387548a11 100644
--- a/src/templating/test/Templates.Test/Helpers/AddFirewallExclusion.cs
+++ b/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
index ab6984537a..ab6984537a 100644
--- a/src/templating/test/Templates.Test/Helpers/AspNetProcess.cs
+++ b/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
index ac71af2697..ac71af2697 100644
--- a/src/templating/test/Templates.Test/Helpers/Npm.cs
+++ b/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
index b244d7df88..b244d7df88 100644
--- a/src/templating/test/Templates.Test/Helpers/ProcessEx.cs
+++ b/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
index 4493d1fb31..4493d1fb31 100644
--- a/src/templating/test/Templates.Test/Helpers/TemplatePackageInstaller.cs
+++ b/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
index 577e900eec..577e900eec 100644
--- a/src/templating/test/Templates.Test/Helpers/TemplateTestBase.cs
+++ b/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
index 04ec47b1ed..04ec47b1ed 100644
--- a/src/templating/test/Templates.Test/Helpers/WebDriverExtensions.cs
+++ b/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
index b3c27e859d..b3c27e859d 100644
--- a/src/templating/test/Templates.Test/Helpers/WebDriverFactory.cs
+++ b/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
index e8bb66ed4b..e8bb66ed4b 100644
--- a/src/templating/test/Templates.Test/MvcTemplateTest.cs
+++ b/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
index 314f72cb1d..314f72cb1d 100644
--- a/src/templating/test/Templates.Test/RazorPagesTemplateTest.cs
+++ b/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
index 7535573fe7..7535573fe7 100644
--- a/src/templating/test/Templates.Test/SpaTemplateTest/AngularTemplateTest.cs
+++ b/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
index 919a76baf3..919a76baf3 100644
--- a/src/templating/test/Templates.Test/SpaTemplateTest/ReactReduxTemplateTest.cs
+++ b/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
index 7238b4930c..7238b4930c 100644
--- a/src/templating/test/Templates.Test/SpaTemplateTest/ReactTemplateTest.cs
+++ b/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
index d56cea0858..d56cea0858 100644
--- a/src/templating/test/Templates.Test/SpaTemplateTest/SpaTemplateTestBase.cs
+++ b/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
index 19230e2958..19230e2958 100644
--- a/src/templating/test/Templates.Test/Templates.Test.csproj
+++ b/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
index c7ca5be7bc..c7ca5be7bc 100644
--- a/src/templating/test/Templates.Test/WebApiTemplateTest.cs
+++ b/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
index 4da1e32213..4da1e32213 100644
--- a/src/templating/test/Templates.Test/template-baselines.json
+++ b/src/Templating/test/Templates.Test/template-baselines.json
diff --git a/src/templating/version.props b/src/Templating/version.props
index 55260ca173..55260ca173 100644
--- a/src/templating/version.props
+++ b/src/Templating/version.props
diff --git a/src/templating/Directory.Build.props b/src/templating/Directory.Build.props
deleted file mode 100644
index 92ebf42a30..0000000000
--- a/src/templating/Directory.Build.props
+++ /dev/null
@@ -1,17 +0,0 @@
-<Project>
- <Import
- Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), AspNetCoreSettings.props))\AspNetCoreSettings.props"
- Condition=" '$(CI)' != 'true' AND '$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), AspNetCoreSettings.props))' != '' " />
-
- <Import Project="version.props" />
- <Import Project="build\dependencies.props" />
- <Import Project="build\sources.props" />
-
- <PropertyGroup>
- <Product>Microsoft ASP.NET Core</Product>
- <RepositoryRoot>$(MSBuildThisFileDirectory)</RepositoryRoot>
- <RepositoryUrl>https://github.com/aspnet/templating</RepositoryUrl>
- <RepositoryType>git</RepositoryType>
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
- </PropertyGroup>
-</Project>